【Java踩坑笔记】47_日期时间计算还在用SimpleDateFormat?线程不安全

【Java踩坑笔记】47_日期时间计算还在用SimpleDateFormat?线程不安全

47 | 日期时间计算还在用 SimpleDateFormat?线程不安全

摘要SimpleDateFormat不是线程安全的,多线程共享一个实例会导致数据错乱甚至抛异常。Java 8+ 用DateTimeFormatter(不可变,线程安全)。


一、问题现象

// ❌ 多线程共享 SimpleDateFormat,数据错乱privatestaticfinalSimpleDateFormatFMT=newSimpleDateFormat("yyyy-MM-dd");publicstaticvoidmain(String[]args){for(inti=0;i<10;i++){newThread(()->{try{System.out.println(FMT.parse("2024-01-01"));}catch(Exceptione){e.printStackTrace();// ❌ 多线程下可能抛异常}}).start();}}

现象:偶尔出现NumberFormatException或解析出错误日期。


二、踩坑现场

场景:Web 项目里共用 SimpleDateFormat

// ❌ 错误:定义为静态字段(多线程共享)@ComponentpublicclassDateService{privatestaticfinalSimpleDateFormatFMT=newSimpleDateFormat("yyyy-MM-dd HH:mm:ss");publicStringformat(Datedate){returnFMT.format(date);// ❌ 多线程并发,结果错乱}}

结果:并发请求下,格式化结果出现错乱日期。


三、原理解析

3.1 为什么线程不安全?

SimpleDateFormat内部用Calendar实例做计算,而Calendar是可变对象:

线程 A:fmt.format(date1) → 修改内部 calendar 字段 线程 B:fmt.format(date2) → 同时修改同一个 calendar → 数据竞争,结果不可预测

3.2 三种错误用法对比

用法问题
静态字段(共用)❌ 线程不安全
每次创建新实例⚠️ 性能差(频繁创建对象)
synchronized⚠️ 并发性能差

四、正确写法

4.1 Java 8+:用DateTimeFormatter(推荐)

// ✅ 不可变,线程安全privatestaticfinalDateTimeFormatterFMT=DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");publicStringformat(LocalDateTimetime){returntime.format(FMT);// ✅ 线程安全}

4.2 Java 7-:用ThreadLocal缓存

// ✅ 每个线程有自己的 SimpleDateFormat 实例privatestaticfinalThreadLocal<SimpleDateFormat>FMT_HOLDER=ThreadLocal.withInitial(()->newSimpleDateFormat("yyyy-MM-dd"));publicStringformat(Datedate){returnFMT_HOLDER.get().format(date);}

4.3 需要 Date ↔ LocalDateTime 互转

// ✅ Java 8+ 互转Datedate=newDate();LocalDateTimetime=date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();LocalDateTimenow=LocalDateTime.now();Datedate2=Date.from(now.atZone(ZoneId.systemDefault()).toInstant());

五、最佳实践

✅ 日期时间处理的 4 条铁律

  1. Java 8+ 全面用java.time包(LocalDateLocalDateTimeDateTimeFormatter
  2. SimpleDateFormat不是线程安全的,不要定义为静态字段
  3. 老代码迁移DateInstantCalendarLocalDateTime**
  4. 时区处理用ZoneId,不要用TimeZone**

🔍java.time包的优势

替换优势
LocalDateDate只有日期,不可变,线程安全
LocalDateTimeCalendar日期+时间,不可变
DateTimeFormatterSimpleDateFormat不可变,线程安全
InstantDate时间戳,时区无关

六、小结

  • SimpleDateFormat不是线程安全的,静态共用会数据错乱
  • Java 8+ 用DateTimeFormatter,不可变,线程安全
  • 老代码用ThreadLocal<SimpleDateFormat>缓存每个线程的实例
  • java.time包全面替换Date/Calendar,不可变且 API 更友好

下一篇预告:用 Optional 消灭 NPE,但别把它当万能药——Optional的设计目的是减少 NPE,但滥用反而让代码更难读。