SimpleDateFormat 为什么线程不安全

SimpleDateFormat是线程不安全的,主要原因如下:

1. 内部状态可变性

复制代码
// SimpleDateFormat 内部维护了可变状态
private StringBuffer format(Date date, StringBuffer toAppendTo, FieldDelegate delegate) {
    // 会修改内部的 Calendar 对象
    calendar.setTime(date);
    // ...
}

2. 共享的 Calendar 实例

每个 SimpleDateFormat对象内部都持有一个 Calendar实例:

复制代码
public class SimpleDateFormat extends DateFormat {
    protected Calendar calendar;  // 共享的可变状态
    
    public String format(Date date) {
        // 1. 设置时间到 calendar
        calendar.setTime(date);
        // 2. 使用 calendar 进行格式化
        return format(calendar);
    }
}

3. 并发问题场景

情况1:多线程同时调用 format()

复制代码
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

// 线程A
sdf.format(dateA);  // 设置 calendar 为 dateA
// 线程B在此刻插入
sdf.format(dateB);  // 设置 calendar 为 dateB
// 线程A继续格式化,但calendar已经被线程B修改

情况2:多线程同时调用 parse()

复制代码
// 线程A
sdf.parse("2024-01-01");
// 线程B
sdf.parse("2024-02-01");
// 两者可能互相干扰,得到错误结果

4. 问题复现代码

复制代码
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

ExecutorService executor = Executors.newFixedThreadPool(10);
List<Future<String>> futures = new ArrayList<>();

for (int i = 0; i < 10; i++) {
    final int index = i;
    futures.add(executor.submit(() -> {
        Date date = new Date(System.currentTimeMillis() + index * 1000);
        return sdf.format(date);  // 可能出现:空指针、格式错误、时间错乱
    }));
}

5. 线程安全的替代方案

方案1:使用 ThreadLocal(推荐)

复制代码
private static final ThreadLocal<SimpleDateFormat> threadLocal =
    ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));

public String formatDate(Date date) {
    return threadLocal.get().format(date);
}

方案2:每次创建新实例

复制代码
public String formatDate(Date date) {
    return new SimpleDateFormat("yyyy-MM-dd").format(date);
}
// 缺点:频繁创建对象,性能较差

方案3:使用 DateTimeFormatter(Java 8+)

复制代码
// DateTimeFormatter 是线程安全的
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

// 格式化
String formatted = LocalDateTime.now().format(formatter);

// 解析
LocalDateTime parsed = LocalDateTime.parse("2024-01-26 10:30:00", formatter);

方案4:使用 FastDateFormat(Apache Commons Lang)

复制代码
FastDateFormat formatter = FastDateFormat.getInstance("yyyy-MM-dd");
String formatted = formatter.format(new Date());  // 线程安全

6. 为什么 DateTimeFormatter 线程安全?

复制代码
public final class DateTimeFormatter {
    // 所有字段都是 final 的
    private final CompositePrinter printer;
    private final CompositeParser parser;
    private final Locale locale;
    
    // 所有方法都是纯函数,不修改内部状态
    public String format(TemporalAccessor temporal) {
        // 不修改任何实例变量
    }
}

总结

  • 根本原因 :SimpleDateFormat内部可变状态(Calendar)在多线程下被共享修改

  • 解决方案:

    1. 使用 ThreadLocal包装(适合传统项目)

    2. 使用 Java 8+ 的 DateTimeFormatter(推荐新项目)

    3. 使用同步锁(性能差,不推荐)

在并发环境下,永远不要共享同一个 SimpleDateFormat实例。

相关推荐
墨云安全7 分钟前
内网资产无法暴露到公网,怎么做安全检测?2026 年内网渗透测试的落地路径
开发语言·安全·网络安全·渗透测试·php
摇滚侠25 分钟前
《计算机是如何工作的:人人都能懂的计算机软硬件工作原理》第 9 章 机器码与汇编语言 阅读笔记 9
java·开发语言·笔记
Cc.Y31 分钟前
Java零基础入门:深入Java核心:文件IO与序列化全解
java·开发语言·python
2601_967212721 小时前
安全型电源轨道系统合规选型技术指标与行业技术路线分析
大数据·经验分享·安全
山东科恩光电1 小时前
安全地毯高灵敏感应机制与耐磨材质优势及多场所防护安装维护指南
安全
goehou1 小时前
Prompt 注入实战:直接注入、间接注入和四道防线(附测试样例)
安全·ai·llm·agent·prompt注入
jimy12 小时前
c++的 Type Traits (一)
开发语言·c++
Web极客码3 小时前
Python Ollama 本地 Agent 入门与订单查询实践
开发语言·jvm·python
kimnoic3 小时前
PHP 8.2的DNF类型怎么用才规范
开发语言·php
世岩清上3 小时前
工厂安全目视化标识不全该怎么整改?标准化落地全流程方案
人工智能·安全·工厂目视化·车间目视化·企业目视化·新建厂房目视化