文章目录一、先说结论SimpleDateFormat 不是线程安全的二、为什么不安全看源码三、解决方案四种方式方案一每次创建新的简单粗暴方案二ThreadLocal推荐方案三synchronized 同步方案四DateTimeFormatter最佳Java 8四、DateTimeFormatter 为什么安全五、线程池场景的隐藏坑SimpleDateFormat 线程安全 全景回答技巧与点评标准回答加分回答面试官点评个人网站线上日志突然出现2023-13-45这样的日期查了半天发现是 SimpleDateFormat 在多线程下出了问题。面试官问SimpleDateFormat 线程安全吗你说不安全追问为什么不安全、“怎么解决”、“DateTimeFormatter 安全吗”就卡住了。今天咱们把这个经典并发 Bug 彻底讲透。一、先说结论SimpleDateFormat 不是线程安全的维度说明是否线程安全❌ 不是不安全原因内部使用 Calendar 对象parse/format 会修改其状态危险操作format()、parse() 都不安全典型表现日期错乱、ArrayIndexOutOfBoundsException、NumberFormatException一句话记住SimpleDateFormat 像公用白板——多人同时写互相覆盖谁也不知道看到的是谁写的。二、为什么不安全看源码SimpleDateFormat 内部持有一个Calendar 对象format() 和 parse() 都会修改它// SimpleDateFormat 源码简化publicclassSimpleDateFormatextendsDateFormat{privateCalendarcalendar;// 共享的可变对象publicStringformat(Datedate){calendar.setTime(date);// ① 设置时间 // ... 使用 calendar 拼字符串StringBuffersbnewStringBuffer();// ② 此时另一个线程可能执行了 calendar.setTime()calendar.get(Calendar.YEAR);// 读到错误的时间returnsb.toString();}}多线程场景线程 Acalendar.setTime(2023-01-01) 线程 Bcalendar.setTime(2024-12-31) → 覆盖了 线程 Acalendar.get(YEAR) → 读到 2024生活类比两个人共用一个计算器——你刚输入 11别人按了 C 清零你按 得到 0一脸懵。可能的异常// 线程 A 正在 parse线程 B 修改了 Calendar 的状态java.lang.ArrayIndexOutOfBoundsExceptionjava.lang.NumberFormatException// 或者更隐蔽返回错误的日期不抛异常三、解决方案四种方式方案一每次创建新的简单粗暴publicStringformatDate(Datedate){SimpleDateFormatsdfnewSimpleDateFormat(yyyy-MM-dd);// 每次新建returnsdf.format(date);}优点简单不会出错。缺点每次创建对象有开销。方案二ThreadLocal推荐privatestaticfinalThreadLocalSimpleDateFormatSDFThreadLocal.withInitial(()-newSimpleDateFormat(yyyy-MM-dd));publicStringformatDate(Datedate){returnSDF.get().format(date);// 每个线程独立实例}优点既线程安全又避免重复创建。缺点线程池场景需要 remove()。方案三synchronized 同步privatestaticfinalSimpleDateFormatSDFnewSimpleDateFormat(yyyy-MM-dd);publicsynchronizedStringformatDate(Datedate){// 加锁returnSDF.format(date);}优点简单。缺点并发性能差所有线程串行。方案四DateTimeFormatter最佳Java 8privatestaticfinalDateTimeFormatterDTFDateTimeFormatter.ofPattern(yyyy-MM-dd);publicStringformatDate(LocalDatedate){returnDTF.format(date);// 线程安全}为什么 DateTimeFormatter 安全它是不可变对象immutable内部没有可变状态所有字段都是 final。四、DateTimeFormatter 为什么安全// DateTimeFormatter 源码publicfinalclassDateTimeFormatter{privatefinalCompositePrinterParserprinterParser;// final privatefinalLocalelocale;// final privatefinalDecimalStyledecimalStyle;// final privatefinalChronologychrono;// final // ... 所有字段都是 final}不可变 天然线程安全。这就是为什么 Java 8 引入的日期时间 APILocalDate、LocalDateTime、DateTimeFormatter都是线程安全的——它们的设计原则就是不可变性。类线程安全原因SimpleDateFormat❌内部 Calendar 可变DateTimeFormatter✅不可变所有字段 finalDateFormat❌内部 Calendar 可变LocalDate/LocalDateTime✅不可变生活类比SimpleDateFormat 像可擦写白板DateTimeFormatter 像印刷好的日历——白板谁都能改日历改不了大家看到的都一样。五、线程池场景的隐藏坑ThreadLocal 方案在线程池中有陷阱// 线程池中线程被复用ThreadLocal 中的值也会残留ExecutorServicepoolExecutors.newFixedThreadPool(10);pool.submit(()-{SDF.set(newSimpleDateFormat(yyyy-MM-dd));// 设置格式 A// 忘了 remove});// 同一个线程被复用SDF.get() 拿到的还是格式 Apool.submit(()-{SDF.get().format(date);// 不是期望的格式});解决用完必须 remove()。try{returnSDF.get().format(date);}finally{SDF.remove();// 必须清理}SimpleDateFormat 线程安全 全景SimpleDateFormat 线程安全 全景 不安全根因 ├── 内部 Calendar 可变 ├── format/parse 修改 Calendar 状态 └── 多线程并发修改导致数据错乱 四种解决方案 ├── 每次新建 ── 简单但有开销 ├── ThreadLocal ── 推荐但线程池要 remove ├── synchronized ── 安全但性能差 └── DateTimeFormatter ── 最佳不可变天然安全 DateTimeFormatter 安全原理 ├── 所有字段 final ├── 不可变对象 └── 天然线程安全 口诀SimpleDateFormat 不安全Calendar 是罪魁 每次新建最简单ThreadLocal 是首选 Java 8 用 DateTimeFormatter不可变最安全 线程池里要 remove否则残留会出事。回答技巧与点评标准回答SimpleDateFormat 不是线程安全的因为它的 format() 和 parse() 方法内部会修改共享的 Calendar 对象的状态多线程并发调用会导致日期错乱或异常。解决方案有四种每次创建新实例简单但有开销、ThreadLocal推荐但线程池需要 remove、synchronized 同步安全但性能差、使用 Java 8 的 DateTimeFormatter最佳方案不可变天然线程安全。实际开发中推荐升级到 DateTimeFormatter。加分回答不变性设计DateTimeFormatter 线程安全的根本原因是不可变设计——所有字段都是 final方法返回新对象而不是修改自身。这是 Java 8 日期时间 API 的设计哲学和 String、BigDecimal 一脉相承。不可变对象不仅线程安全还能安全地缓存和共享FastDateFormatApache Commons Lang 提供了 FastDateFormat是 SimpleDateFormat 的线程安全替代品底层用 ThreadLocal 实现。如果项目无法升级到 Java 8可以用它Joda-Time 的启示Java 8 日期时间 API 的设计深受 Joda-Time 影响。Joda-Time 中的 DateTimeFormatter 一直是线程安全的这也证明了不可变设计是日期格式化器的正确选择面试官点评这道题考的是你对线程安全实际问题的敏感度。能说出SimpleDateFormat 不安全、因为 Calendar 可变是基本要求能给出多种解决方案并分析优劣才算及格。如果你能从不可变设计的角度解释 DateTimeFormatter 为什么安全、提到线程池场景的 ThreadLocal 残留问题面试官会认为你不只是背答案而是在实践中真正踩过坑、思考过解决方案。原文阅读内容有帮助点赞、收藏、关注三连评论区等你