SimpleDateFormat的线程不安全问题

由于个人对高并发知识基础非常薄弱,最近有在学习高并发的一些知识,今天记录一下平时不太注意可能使用较多的SimpleDateFormat的线程不安全问题。

验证SimpleDateFormat的线程不安全

public class Test {
    private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd");
    // 日期字符串
    private static final String DATE_STRING = "2025-06-17";
    // 期望解析的正确日期(毫秒值)
    private static final long EXPECTED_DATE = 1750089600000L;
    // 线程池大小
    private static final int THREAD_COUNT = 10;
    // 执行次数
    private static final int EXECUTION_COUNT = 1000;
    public static void main(String[] args) throws InterruptedException {
        ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT);
        int[] errorCount = {0}; // 用于统计错误次数
        // 提交多个任务到线程池
        for (int i = 0; i < THREAD_COUNT; i++) {
            executor.submit(() -> {
                for (int j = 0; j < EXECUTION_COUNT; j++) {
                    try {
                        // 解析日期
                       Date date = SDF.parse(DATE_STRING);
                        // 验证解析结果是否正确
                        if (date.getTime() != EXPECTED_DATE) {
                            synchronized (errorCount) {
                                errorCount[0]++;
                                System.out.printf("线程 %s 解析结果错误:%s%n",
                                        Thread.currentThread().getName(),
                                        date);
                            }
                        }
                    } catch (ParseException e) {
                        synchronized (errorCount) {
                            errorCount[0]++;
                            System.out.printf("线程 %s 解析异常:%s%n",
                                    Thread.currentThread().getName(),
                                    e.getMessage());
                        }
                    } catch (Exception e) {
                        synchronized (errorCount) {
                            errorCount[0]++;
                            System.out.printf("线程 %s 意外异常:%s%n",
                                    Thread.currentThread().getName(),
                                    e.getMessage());
                        }
                    }
                }
            });
        }

        // 关闭线程池并等待所有任务完成
        executor.shutdown();
        executor.awaitTermination(1, TimeUnit.MINUTES);
        // 输出结果
        System.out.printf("执行完成,共发现 %d 个错误%n", errorCount[0]);
        if (errorCount[0] > 0) {
            System.out.println("该方法不是线程安全的!");
        } else {
            System.out.println("全部执行成功,该方法线程安全");
        }
    }
}

运行方法得到如下结果,从结果可以知道有639个结果是错误的,所以SimpleDateFormat是线程不安全。SimpleDateFormat之所以线程不安全,是因为它内部使用一个共享的Calendar对象来处理日期转换。当多个线程同时使用同一个SimpleDateFormat 实例时,它们会共享这个 Calendar 对象。

线程安全解决

(1)ThreadLocal.get()  为每个线程创建独立的 SimpleDateFormat实例。(此方法效率比较高)相比于synchronized (simpleDateFormat){ simpleDateFormat.parse("2025-06-17");}以及Lock锁方式效率更高。

定义threadLocal,再进行转化日期。

private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>() {
        @Override
        protected DateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd");
        }
    };
//  private static final ThreadLocal<SimpleDateFormat> threadLocal = ThreadLocal.withInitial(
   //     () -> new SimpleDateFormat("yyyy-MM-dd")
 //   );
 Date date = threadLocal.get().parse(DATE_STRING);

最后结果如下:

(2)使用DateTimeFormatter,它是线程安全的!!!

public class Test {
    // 日期字符串
    private static final String DATE_STRING = "2025-06-17";
    // 线程池大小
    private static final int THREAD_COUNT = 10;
    // 每个线程的执行次数
    private static final int EXECUTION_COUNT = 1000;
    private static final LocalDate EXPECTED_DATE = LocalDate.of(2025, 6, 17);
    private static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    public static void main(String[] args) throws InterruptedException {
        ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT);
        int[] errorCount = {0}; // 用于统计错误次数

        // 提交多个任务到线程池
        for (int i = 0; i < THREAD_COUNT; i++) {
            executor.submit(() -> {
                for (int j = 0; j < EXECUTION_COUNT; j++) {
                    try {
                        LocalDate date = LocalDate.parse(DATE_STRING,formatter);

                        // 验证解析结果是否正确
                        if (!date.equals(EXPECTED_DATE)) {
                            synchronized (errorCount) {
                                errorCount[0]++;
                                System.out.printf("线程 %s 解析结果错误:%s%n",
                                        Thread.currentThread().getName(),
                                        date);
                            }
                        }
                    } catch (Exception e) {
                        synchronized (errorCount) {
                            errorCount[0]++;
                            System.out.printf("线程 %s 解析异常:%s%n",
                                    Thread.currentThread().getName(),
                                    e.getMessage());
                        }
                    }
                }
            });
        }

        // 关闭线程池并等待所有任务完成
        executor.shutdown();
        executor.awaitTermination(1, TimeUnit.MINUTES);

        // 输出结果
        System.out.printf("执行完成,共发现 %d 个错误%n", errorCount[0]);
        if (errorCount[0] > 0) {
            System.out.println("该方法不是线程安全的!");
        } else {
            System.out.println("全部执行成功,该方法线程安全");
        }
    }
}

(3)使用第三方依赖库jdoda-time的DateTImeFormat方法

<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.9.9</version>
</dependency>
//
private static DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern("yyyy-MM-dd");
 Date date = DateTime.parse("2025-06-17", dateTimeFormatter).toDate();

最近学了两个命令

(1)jps命令:查看进程信息

(2)jstack

用于生成 Java 虚拟机(JVM)当前时刻的线程快照(thread dump)。线程快照记录了各线程的执行状态、调用栈信息,常用于分析线程死锁、阻塞、CPU 过高、内存泄漏等问题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值