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实例

相关推荐
aaaffaewrerewrwer2 小时前
免费在线 AVIF 转 WebP 工具推荐(支持批量转换 + 浏览器本地处理 + 无需上传)
安全·个人开发
xieliyu.2 小时前
Java算法精讲:双指针(三)
java·开发语言·算法
zhengfei6113 小时前
【渗透工具】Payloader — 渗透测试辅助平台(payload一键所有)
网络·安全·web安全
CryptoPP3 小时前
快速对接东京证券交易所API数据:实战指南与代码示例
开发语言·人工智能·windows·python·信息可视化·区块链
ZC跨境爬虫4 小时前
跟着 MDN 学JavaScript day_7:数学运算与逻辑判断实战测试
开发语言·前端·javascript·学习·ecmascript
Multipath7125 小时前
无人区不掉线:多链路聚合路由,为环塔拉力赛筑起“空中通讯走廊”
网络·5g·安全·无人机·实时音视频
阳区欠5 小时前
【LangChain】LLM基础介绍
开发语言·python·langchain
Jinkxs5 小时前
Java 跨域14-Java 与区块链(Hyperledger)集成
java·开发语言·区块链
知识浅谈5 小时前
人工智能日报 每日AI新闻(2026年6月8日):OpenAI安全加码、苹果AI升级前夜与国产AI应用落地
人工智能·安全·chatgpt
晨曦中的暮雨6 小时前
Golang速通(Javaer版)
java·开发语言·后端·golang