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

📅 发布时间:2026/7/15 21:15:18 👁️ 浏览次数:
【Java踩坑笔记】47_日期时间计算还在用SimpleDateFormat?线程不安全
47 | 日期时间计算还在用 SimpleDateFormat线程不安全摘要SimpleDateFormat不是线程安全的多线程共享一个实例会导致数据错乱甚至抛异常。Java 8 用DateTimeFormatter不可变线程安全。一、问题现象// ❌ 多线程共享 SimpleDateFormat数据错乱privatestaticfinalSimpleDateFormatFMTnewSimpleDateFormat(yyyy-MM-dd);publicstaticvoidmain(String[]args){for(inti0;i10;i){newThread(()-{try{System.out.println(FMT.parse(2024-01-01));}catch(Exceptione){e.printStackTrace();// ❌ 多线程下可能抛异常}}).start();}}现象偶尔出现NumberFormatException或解析出错误日期。二、踩坑现场场景Web 项目里共用 SimpleDateFormat// ❌ 错误定义为静态字段多线程共享ComponentpublicclassDateService{privatestaticfinalSimpleDateFormatFMTnewSimpleDateFormat(yyyy-MM-dd HH:mm:ss);publicStringformat(Datedate){returnFMT.format(date);// ❌ 多线程并发结果错乱}}结果并发请求下格式化结果出现错乱日期。三、原理解析3.1 为什么线程不安全SimpleDateFormat内部用Calendar实例做计算而Calendar是可变对象线程 Afmt.format(date1) → 修改内部 calendar 字段 线程 Bfmt.format(date2) → 同时修改同一个 calendar → 数据竞争结果不可预测3.2 三种错误用法对比用法问题静态字段共用❌ 线程不安全每次创建新实例⚠️ 性能差频繁创建对象加synchronized⚠️ 并发性能差四、正确写法4.1 Java 8用DateTimeFormatter推荐// ✅ 不可变线程安全privatestaticfinalDateTimeFormatterFMTDateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm:ss);publicStringformat(LocalDateTimetime){returntime.format(FMT);// ✅ 线程安全}4.2 Java 7-用ThreadLocal缓存// ✅ 每个线程有自己的 SimpleDateFormat 实例privatestaticfinalThreadLocalSimpleDateFormatFMT_HOLDERThreadLocal.withInitial(()-newSimpleDateFormat(yyyy-MM-dd));publicStringformat(Datedate){returnFMT_HOLDER.get().format(date);}4.3 需要 Date ↔ LocalDateTime 互转// ✅ Java 8 互转DatedatenewDate();LocalDateTimetimedate.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();LocalDateTimenowLocalDateTime.now();Datedate2Date.from(now.atZone(ZoneId.systemDefault()).toInstant());五、最佳实践✅ 日期时间处理的 4 条铁律Java 8 全面用java.time包LocalDate、LocalDateTime、DateTimeFormatterSimpleDateFormat不是线程安全的不要定义为静态字段老代码迁移Date→InstantCalendar→LocalDateTime**时区处理用ZoneId不要用TimeZone**java.time包的优势类替换优势LocalDateDate只有日期不可变线程安全LocalDateTimeCalendar日期时间不可变DateTimeFormatterSimpleDateFormat不可变线程安全InstantDate时间戳时区无关六、小结SimpleDateFormat不是线程安全的静态共用会数据错乱Java 8 用DateTimeFormatter不可变线程安全老代码用ThreadLocalSimpleDateFormat缓存每个线程的实例java.time包全面替换Date/Calendar不可变且 API 更友好下一篇预告用 Optional 消灭 NPE但别把它当万能药——Optional的设计目的是减少 NPE但滥用反而让代码更难读。