由于个人对高并发知识基础非常薄弱,最近有在学习高并发的一些知识,今天记录一下平时不太注意可能使用较多的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 过高、内存泄漏等问题