Java旧日期类线程不安全,因Date、Calendar、SimpleDateFormat内部状态可变且无同步机制;Java 8引入LocalDateTime等不可变类及DateTimeFormatter解决该问题。

因为它们内部状态可变,且没有同步保护机制。
核心问题在于可变性与共享状态
Date 类本身是可变的——它的毫秒值可以通过 setTime() 等方法直接修改;Calendar 更明显,它本质是一个带状态的“日历计算引擎”,内部持有 Calendar 的字段(如 year、month、day)和一个可复用的 Calendar 实例(比如 DateFormat 中的 calendar 字段)。多个线程共用同一个实例时,彼此的操作会相互覆盖。
- Date.setTime() 会直接改写对象内部的 time 值,无锁也无副本
- Calendar 的 get()/set()、add()、roll() 等方法都依赖并修改内部字段数组和 timeInMillis,非原子操作
- SimpleDateFormat 内部持有一个 Calendar 引用,在 parse() 和 format() 过程中反复调用 clear() → set() → getTime(),中间状态极易被其他线程打断
API 设计未考虑并发场景
JDK 1.0–1.7 的日期类诞生于单线程或粗粒度同步为主的年代。它们既不是 final 类,字段也未用 volatile 或不可变封装,更没有提供线程隔离的默认策略。
- 没有内置 ThreadLocal 封装逻辑,开发者需自行处理(如用 static + synchronized,或 ThreadLocal<SimpleDateFormat>)
- getInstance() 返回的 Calendar 是新实例,但若被缓存为 static,就变成共享资源
- 所有方法均未加 synchronized,也没有使用 java.util.concurrent 工具类做协调
典型崩坏现场:SimpleDateFormat 并发解析
当 10 个线程同时调用同一个 static SimpleDateFormat 的 parse("20230101") 时,常见报错包括:
- java.lang.NumberFormatException:解析出 "20230001" 或 "00000000" —— 因为线程 A 刚 set 年份,线程 B 就执行了 clear()
- java.lang.ArrayIndexOutOfBoundsException:Calendar 内部字段数组越界 —— 多线程同时操作字段索引所致
- 返回 null 或非法 Date 实例 —— establish() 过程被中断,calendar 处于不一致状态
Java 8 的解法:不可变 + 无状态
LocalDate、LocalDateTime、DateTimeFormatter 等类全部声明为 final,所有字段 private final,任何“修改”操作(如 plusDays、withHour)都返回新对象,内部不复用可变状态。
- DateTimeFormatter 是不可变的,可安全定义为 static final
- LocalDateTime.now() 每次返回全新实例,线程间零干扰
- 时区、格式化、计算等职责分离清晰,不再靠一个 Calendar 对象包打天下

















