SimpleDateFormat是线程不安全的,主要原因如下:
1. 内部状态可变性
// SimpleDateFormat 内部维护了可变状态
private StringBuffer format(Date date, StringBuffer toAppendTo, FieldDelegate delegate) {
// 会修改内部的 Calendar 对象
calendar.setTime(date);
// ...
}
2. 共享的 Calendar 实例
每个 SimpleDateFormat对象内部都持有一个 Calendar实例:
public class SimpleDateFormat extends DateFormat {
protected Calendar calendar; // 共享的可变状态
public String format(Date date) {
// 1. 设置时间到 calendar
calendar.setTime(date);
// 2. 使用 calendar 进行格式化
return format(calendar);
}
}
3. 并发问题场景
情况1:多线程同时调用 format()
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
// 线程A
sdf.format(dateA); // 设置 calendar 为 dateA
// 线程B在此刻插入
sdf.format(dateB); // 设置 calendar 为 dateB
// 线程A继续格式化,但calendar已经被线程B修改
情况2:多线程同时调用 parse()
// 线程A
sdf.parse("2024-01-01");
// 线程B
sdf.parse("2024-02-01");
// 两者可能互相干扰,得到错误结果
4. 问题复现代码
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
ExecutorService executor = Executors.newFixedThreadPool(10);
List<Future<String>> futures = new ArrayList<>();
for (int i = 0; i < 10; i++) {
final int index = i;
futures.add(executor.submit(() -> {
Date date = new Date(System.currentTimeMillis() + index * 1000);
return sdf.format(date); // 可能出现:空指针、格式错误、时间错乱
}));
}
5. 线程安全的替代方案
方案1:使用 ThreadLocal(推荐)
private static final ThreadLocal<SimpleDateFormat> threadLocal =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
public String formatDate(Date date) {
return threadLocal.get().format(date);
}
方案2:每次创建新实例
public String formatDate(Date date) {
return new SimpleDateFormat("yyyy-MM-dd").format(date);
}
// 缺点:频繁创建对象,性能较差
方案3:使用 DateTimeFormatter(Java 8+)
// DateTimeFormatter 是线程安全的
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// 格式化
String formatted = LocalDateTime.now().format(formatter);
// 解析
LocalDateTime parsed = LocalDateTime.parse("2024-01-26 10:30:00", formatter);
方案4:使用 FastDateFormat(Apache Commons Lang)
FastDateFormat formatter = FastDateFormat.getInstance("yyyy-MM-dd");
String formatted = formatter.format(new Date()); // 线程安全
6. 为什么 DateTimeFormatter 线程安全?
public final class DateTimeFormatter {
// 所有字段都是 final 的
private final CompositePrinter printer;
private final CompositeParser parser;
private final Locale locale;
// 所有方法都是纯函数,不修改内部状态
public String format(TemporalAccessor temporal) {
// 不修改任何实例变量
}
}
总结
-
根本原因 :
SimpleDateFormat内部可变状态(Calendar)在多线程下被共享修改 -
解决方案:
-
使用
ThreadLocal包装(适合传统项目) -
使用 Java 8+ 的
DateTimeFormatter(推荐新项目) -
使用同步锁(性能差,不推荐)
-
在并发环境下,永远不要共享同一个 SimpleDateFormat实例。