SimpleDateFormat
为什么不是线程安全的?
核心问题:内部状态可变
SimpleDateFormat
继承自 DateFormat
,而 DateFormat
内部维护了一个 Calendar
实例:
// SimpleDateFormat.java
private Calendar calendar;
这个 calendar
在 format()
和 parse()
时被反复修改:
public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition pos) {// 清除日历字段calendar.clear();// 设置时间calendar.setTime(date); // 修改内部状态// 格式化...return toAppendTo;
}
多线程并发调用时:
- 线程 A 调用
format(date1)
- 线程 B 同时调用
format(date2)
- 两者共享同一个
calendar
- 状态被互相覆盖 → 日期错乱、解析错误!
❌
SimpleDateFormat
是典型的“可变状态 + 共享实例”反模式!
实战:多线程下的诡异 Bug 演示
测试代码
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 100; i++) {final String dateStr = "2025-01-" + String.format("%02d", (i % 31) + 1);executor.submit(() -> {try {Date parsed = sdf.parse(dateStr);String formatted = sdf.format(parsed);// 预期:2025-01-01 ~ 2025-01-31// 实际:可能输出 2025-01-31 或 1970-01-01 等错误值!System.out.println(dateStr + " -> " + formatted);} catch (ParseException e) {e.printStackTrace();}});
}
可能输出结果
2025-01-01 -> 2025-01-31
2025-01-02 -> 2025-01-31
2025-01-03 -> java.lang.NumberFormatException
...
🔥 日期被污染,结果完全不可预测!
解决方案一:synchronized
同步(性能差)
private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");public static String format(Date date) {synchronized (SDF) {return SDF.format(date);}
}
✅ 线程安全
❌ 性能极差:所有线程串行执行,高并发下成为瓶颈
解决方案二:ThreadLocal
(经典但繁琐)
private static final ThreadLocal<SimpleDateFormat> SDF_THREAD_LOCAL = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));public static String format(Date date) {return SDF_THREAD_LOCAL.get().format(date);
}public static Date parse(String dateStr) throws ParseException {return SDF_THREAD_LOCAL.get().parse(dateStr);
}
✅ 线程安全
✅ 性能较好
⚠️ 内存泄漏风险:ThreadLocal
持有 SimpleDateFormat
,若线程池长期运行,需注意清理
解决方案三:Java 8 java.time
+ DateTimeFormatter
(推荐!)
Java 8 引入了全新的日期时间 API:java.time
核心优势
- 不可变设计:所有类(如
LocalDateTime
、ZonedDateTime
)都是final
且不可变 - 无状态格式化器:
DateTimeFormatter
是 不可变、无状态、线程安全 的!
正确用法
// 预定义的常量(线程安全)
DateTimeFormatter ISO_FORMAT = DateTimeFormatter.ISO_LOCAL_DATE_TIME;// 自定义格式(线程安全)
DateTimeFormatter CUSTOM_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");// 使用
LocalDateTime now = LocalDateTime.now();
String formatted = now.format(CUSTOM_FORMAT);LocalDateTime parsed = LocalDateTime.parse("2025-01-01 12:00:00", CUSTOM_FORMAT);
✅ 线程安全:DateTimeFormatter
无内部状态
✅ 高性能:无需同步或 ThreadLocal
✅ 功能强大:支持时区、本地化、复杂格式