問(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í)如果其他線(xiàn)程同時(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的壓力赦肃。