simpledateformat 多线程复用不安全,因其内部 calendar 可变且非原子操作导致解析错乱;推荐 java 8+ 使用线程安全的 datetimeformatter,旧版本则每次新建实例。

SimpleDateFormat 在多线程环境下直接复用会出问题,核心原因是它内部持有可变的 Calendar 实例,而这个对象被多个线程并发读写,导致解析错乱、抛 NumberFormatException 或返回错误日期(比如把 "2023-01-01" 解成 2023-12-01)。这不是偶发 bug,压测时极易暴露。
为什么 SimpleDateFormat 是线程不安全的
它不是无状态类——parse() 和 format() 都会修改内部 calendar 字段:
-
calendar.clear()和calendar.set(...)是两步非原子操作,线程 A 清空后、未设值前,线程 B 可能已开始读取或写入 - 多个线程共用一个实例时,
calb.establish(calendar)返回的结果就不可预测 - 即使加
synchronized,也容易漏锁、降低吞吐,且难以覆盖所有调用点
Java 8 之前常见的错误用法
这些写法在高并发下都危险:
- 声明为
static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd")—— 全局共享,最典型雷区 - 放在 Spring Bean 中作为成员变量 —— 被多个请求线程反复调用
- 用
ThreadLocal<simpledateformat></simpledateformat>但没配合remove()—— 线程池中线程复用导致内存泄漏
推荐的替代方案(Java 8+)
优先使用 DateTimeFormatter,它是不可变(immutable)、线程安全、零配置共享的:
- 预定义常量可直接用:
DateTimeFormatter.ISO_LOCAL_DATE、DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") - 解析返回
LocalDate/LocalDateTime,如需Date:用localDateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() - 注意大小写:
"yyyy-MM-dd"正确,"YYYY-MM-DD"是错的(Y 是 week-year,D 是年中第几天) - 默认严格匹配,如需宽松解析(忽略尾部空格等),要用
DateTimeFormatterBuilder显式配置
如果必须兼容旧代码(Java 7 或低版本)
安全但略重的兜底做法:
- 每次解析/格式化都新建实例:
new SimpleDateFormat("yyyy-MM-dd")—— 简单、可靠、易排查 - 避免
ThreadLocal手动管理;若真要用,务必在 finally 块中调用threadLocal.remove() - 不要试图给
SimpleDateFormat加锁封装 —— 容易遗漏,且锁粒度难控制
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











