simpledateformat线程不安全的根本原因是其内部共享且可变的calendar对象被多线程并发修改,parse/format操作非原子、无同步保护,导致状态污染和数据错乱;而datetimeformatter不可变、无状态、线程安全。

旧版 SimpleDateFormat 在高并发多线程下报错,根本原因不是“偶尔出问题”,而是它设计上就不支持共享使用——只要多个线程共用同一个实例,错误就必然发生。
内部 Calendar 是共享且可变的
SimpleDateFormat 解析或格式化时,全程依赖一个内部的 Calendar 对象。这个对象不是每次调用都新建,而是被反复 clear()、set()、getTime()。多个线程同时操作它,就像多人共用一支笔在一张纸上写:A刚写下“2024”,B立刻涂改成“13”,A再接着写“-01”——结果就是“2024-13-01”,后续计算直接溢出。
- 典型报错:
NumberFormatException: For input string: "13" - 静默错乱:输出 “2025-99-88 77:66:55” 这类非法时间
- 堆栈常指向
CalendarBuilder.addYear()或parsedDate字段
关键操作非原子,也没有同步保护
它的 parse() 和 format() 方法里,状态更新分多步完成,比如:
- 先清空 calendar(
clear()) - 再逐个设年、月、日(
set(Calendar.YEAR, ...)) - 最后取毫秒值(
calendar.getTimeInMillis())
这些步骤之间没有任何锁或原子性保障。一个线程执行到一半被挂起,另一个线程进来覆盖字段,前者恢复后读的已是脏数据。
连只读操作也不安全
有人以为“我只调 parse(),不改东西,应该没事”——这是误区。因为 parse() 本身就要修改 calendar 和 numberFormat.parsePosition 等内部字段,本身就是破坏性操作。JDK 文档明确写着:“Date formats are not synchronized. It is recommended to create separate format instances for each thread.”
为什么 DateTimeFormatter 没这问题
DateTimeFormatter 是不可变(immutable)的:所有属性 final,构造后状态零修改;每次 format() 或 parse() 都基于传入的 LocalDateTime 独立计算,不复用、不缓存、不共享任何中间状态。
- 可放心定义为
static final - 无需
synchronized,也无需ThreadLocal - 线程间完全隔离,吞吐量不打折











