SimpleDateFormat 为什么不是线程安全的?

核心问题:内部状态可变

SimpleDateFormat 继承自 DateFormat,而 DateFormat 内部维护了一个 Calendar 实例

// SimpleDateFormat.java
private Calendar calendar;

这个 calendarformat()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

核心优势

  • 不可变设计:所有类(如 LocalDateTimeZonedDateTime)都是 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功能强大:支持时区、本地化、复杂格式