【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 条铁律
- Java 8+ 全面用
java.time包(LocalDate、LocalDateTime、DateTimeFormatter) SimpleDateFormat不是线程安全的,不要定义为静态字段- 老代码迁移:
Date→Instant,Calendar→LocalDateTime** - 时区处理用
ZoneId,不要用TimeZone**
🔍java.time包的优势
| 类 | 替换 | 优势 |
|---|---|---|
LocalDate | Date | 只有日期,不可变,线程安全 |
LocalDateTime | Calendar | 日期+时间,不可变 |
DateTimeFormatter | SimpleDateFormat | 不可变,线程安全 |
Instant | Date | 时间戳,时区无关 |
六、小结
SimpleDateFormat不是线程安全的,静态共用会数据错乱- Java 8+ 用
DateTimeFormatter,不可变,线程安全 - 老代码用
ThreadLocal<SimpleDateFormat>缓存每个线程的实例 java.time包全面替换Date/Calendar,不可变且 API 更友好
下一篇预告:用 Optional 消灭 NPE,但别把它当万能药——Optional的设计目的是减少 NPE,但滥用反而让代码更难读。
