不能直接复用同一个simpledateformat实例,因其内部calendar和numberformat可变,多线程调用parse/format会导致日期错乱或异常;推荐使用线程安全的datetimeformatter(java 8+),或threadlocal封装simpledateformat,或每次新建实例。

不能直接复用同一个 SimpleDateFormat 实例,它内部持有可变的 Calendar 和 NumberFormat 对象,多线程并发调用 parse() 或 format() 会互相覆盖状态,轻则日期错乱(如“2023-01-01”变成“2023-13-01”),重则抛 NumberFormatException 或 ParseException。
推荐方案:用 DateTimeFormatter(Java 8+)
这是最干净、安全、现代的做法。它不可变(immutable)、线程安全,可全局复用:
- 预定义常量直接用:
DateTimeFormatter.ISO_LOCAL_DATE、DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") - 格式化
LocalDateTime:localDateTime.format(formatter) - 解析字符串得
LocalDateTime:LocalDateTime.parse("2023-01-01 12:00:00", formatter) - 需转回
Date时,用:localDateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()
兼容旧版:用 ThreadLocal 封装 SimpleDateFormat
若必须用 SimpleDateFormat(如维护老项目),ThreadLocal 是比加锁更优的选择,但要注意初始化细节:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 必须用
ThreadLocal.withInitial(),避免 Android 或旧 JDK 兼容问题 - 每次创建新实例时显式设时区:
sdf.setTimeZone(TimeZone.getTimeZone("GMT+8")),否则依赖 JVM 默认时区,易出偏差 - 建议关闭宽松模式:
sdf.setLenient(false),防止“2023-02-30”被静默转成“2023-03-02” - 不要在
get()后调applyPattern(),会污染当前线程实例,破坏隔离性
简单兜底:每次用都 new 一个实例
适用于低频、非性能敏感场景(如工具类、脚本、日志打点):
- 代码清晰,无共享,天然线程安全
- 开销可控——JVM 对短生命周期对象优化很好,GC 压力极小
- 适合模式固定、调用不频繁的场合,比如按固定格式打印启动时间
不推荐做法
别用 static 成员变量:Spring Controller、工具类里声明 static final SimpleDateFormat 是典型踩坑点,多个请求共用必然出错。
慎用 synchronized:虽然能保安全,但所有线程串行排队,高并发下吞吐骤降,得不偿失。
别信“只读就安全”:哪怕只调 parse(),内部 calendar.clear() 和 set() 仍是破坏性操作,照样线程不安全。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










