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实例。