问题复现
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();
}
});
}
}
}
执行结果
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类,其中有一个Calendar对象,用于format和parse时计算时间。每次执行parse()时,会先将Calendar对象清空后重新计算得到新的值,这个过程是没有加锁的,此时如果其他线程同时在执行,则会导致获取不到Calendar对象正确的值,最终parse的结果出错。
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结合使用(推荐)
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类
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类
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加锁
-
每次转换时new一个SimpleDateFormat实例
这两种方法都不推荐:前者在并发很高时会导致显著的性能下降,而后者会创建大量一次性对象,加大GC的压力。
网友评论