simpledateformat 线程不安全,因其内部复用可变 calendar 实例,多线程调用 parse() 或 format() 时会相互覆盖年、月、日等字段,必然引发数据竞争与状态错乱。

因为 SimpleDateFormat 内部持有可变的共享 Calendar 实例,多线程同时调用 parse() 或 format() 时会互相覆盖其字段(如年、月、日、毫秒),导致状态错乱——这不是“偶尔出错”,而是必然发生的数据竞争。
核心问题:Calendar 被多个线程反复污染
SimpleDateFormat 继承自 DateFormat,其所有解析和格式化逻辑都依赖同一个内部 Calendar 对象。这个对象不是每次调用都新建,而是被复用并持续修改:
- 线程 A 开始解析
"2026-06-05 10:00:00",把 Calendar 的年设为 2026、月设为 6; - "2025-13-01 09:00:00"(含非法月份 13),把 Calendar 的月强行改成 13;
- NumberFormatException: For input string: "13" 或返回错误日期(如显示成 2027-01-01)。
连 format() 都不安全
很多人误以为只在 parse() 时危险,其实 format() 同样依赖 Calendar 的 getTimeInMillis() 计算毫秒值。并发调用时,Calendar 的时区、毫秒、字段缓存等均可能被干扰,造成:
- 本该输出
"2026-06-05",却变成"1970-01-01"; - 同一输入在不同请求中格式化结果不一致;
- 偶发
ArrayIndexOutOfBoundsException(内部数组索引越界)。
为什么加 synchronized 不是好办法
虽然加锁能避免状态冲突,但代价极高:
- 所有线程必须排队等待 formatter,吞吐量断崖式下降;
- 在高并发 Web 接口(如每秒上千请求)中,它会成为明显瓶颈;
- 锁粒度大,无法并行处理,违背现代服务对响应时效的要求。
真正可靠的解法是换掉它
Java 8 引入的 DateTimeFormatter 是不可变(immutable)、无共享状态的:
- 每次
format()或parse()都基于传入的LocalDateTime等只读对象计算,自身零修改; - 可放心定义为
static final全局复用,无需任何同步; - 注意迁移细节:模式串大小写敏感(
MM是月,mm是分)、YYYY≠yyyy、中文星期需显式指定Locale.CHINA。











