SimpleDateFormat線程不安全問(wèn)題與解決思路

問(wèn)題復(fù)現(xiàn)

public class SimpleDateFormatDemo {

    public static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static void main(String[] args) throws Exception {
        ExecutorService pool = Executors.newFixedThreadPool(20);
        for (int i = 0; i < 200; i++) {
            pool.execute(() -> {
                try {
                    System.out.println(sdf.parse("2022-04-15 17:02:00"));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
    }
}

執(zhí)行結(jié)果

Fri Apr 15 17:02:00 CST 2022
Fri Apr 15 17:02:00 CST 2022
Fri Apr 15 17:02:00 CST 2022
Wed Apr 15 17:02:00 CST 44
Tue Apr 15 17:02:00 CST 2200
Wed Apr 15 17:02:00 CST 44
Sun Apr 17 05:42:00 CST 2022
Fri Apr 15 17:02:00 CST 2022
Mon Apr 15 17:02:00 CST 1720
Mon Apr 18 00:35:20 CST 2022
Thu Apr 15 17:02:00 CST 1
Wed Mar 31 17:02:00 CST 1
Fri Apr 15 17:02:00 CST 2022
Thu Apr 15 17:02:00 CST 4202
Fri Apr 15 17:02:00 CST 2022
Fri Apr 15 21:40:00 CST 2022

原因分析

SimpleDateFormat繼承于DateFormat類(lèi),其中有一個(gè)Calendar對(duì)象,用于format和parse時(shí)計(jì)算時(shí)間。每次執(zhí)行parse()時(shí),會(huì)先將Calendar對(duì)象清空后重新計(jì)算得到新的值,這個(gè)過(guò)程是沒(méi)有加鎖的,此時(shí)如果其他線程同時(shí)在執(zhí)行,則會(huì)導(dǎo)致獲取不到Calendar對(duì)象正確的值,最終parse的結(jié)果出錯(cuò)。


SimpleDateFormat 的 JavaDoc 也明確指出:

Date formats are not synchronized. It is recommended to create separate format instances for each thread. If multiple threads access a format concurrently, it must be synchronized externally.

解決思路

1. 與ThreadLocal結(jié)合使用(推薦)

public class SimpleDateFormatDemo {
    
    private static final ThreadLocal<SimpleDateFormat> formatter = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

    public static void main(String[] args) throws Exception {
        ExecutorService pool = Executors.newFixedThreadPool(20);
        for (int i = 0; i < 200; i++) {
            pool.execute(() -> {
                try {
                    System.out.println(formatter.get().parse("2022-04-15 17:02:00"));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
    }
}

2. 使用apache commons-lang包的FastDateFormat類(lèi)

FastDateFormat is a fast and thread-safe version of java.text.SimpleDateFormat.

format和parse的使用方式如下:

FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss").format(new Date());
FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss").parse("2022-04-15 17:02:00")

3. 使用Java8提供的DateTimeFormatter類(lèi)

format和parse的使用方式如下:

LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime.parse("2022-04-15 17:02:00", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));

4. 其他思路:

  • 使用Synchronized或ReentrantLock加鎖

  • 每次轉(zhuǎn)換時(shí)new一個(gè)SimpleDateFormat實(shí)例

這兩種方法都不推薦:前者在并發(fā)很高時(shí)會(huì)導(dǎo)致顯著的性能下降,而后者會(huì)創(chuàng)建大量一次性對(duì)象,加大GC的壓力。

參考文章

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容