ThreadLocal解决SimpleDateFormat线程安全问题
本文演示了SimpleDateFormat的线程安全问题及解决方案。通过两个内部类对比展示:
UnsafeDateFormatter:直接共享SimpleDateFormat实例,多线程并发使用时会出现竞态条件,导致格式化结果不一致
SafeDateFormatter:使用ThreadLocal为每个线程创建独立的SimpleDateFormat实例,确保线程安全
测试方法通过100个并发任务验证两种方式的差异,安全版本能保证格式化-解析-再格式化的结果一致性
提供了cleanup()方法清理ThreadLocal变量防止内存泄漏
关键点:ThreadLocal通过线程隔离解决共享资源竞争问题,适合维护线程不安全的类实例。
/**
* 演示SimpleDateFormat线程安全问题及解决方案的示例类
*
* 本类通过两个内部类UnsafeDateFormatter和SafeDateFormatter展示了SimpleDateFormat的线程安全问题,
* 并提供了使用ThreadLocal的解决方案
*/
public class ThreadLocalSimpleDateFormatDemoCorrected {
/**
* 线程不安全的日期格式化器
* 直接共享SimpleDateFormat实例,在多线程环境下会出现竞态条件
*/
public static class UnsafeDateFormatter {
private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
/**
* 格式化日期
*
* @param date 要格式化的日期
* @return 格式化后的字符串
*/
public static String format(Date date) {
return sdf.format(date);
}
/**
* 解析日期字符串
*
* @param dateString 日期字符串
* @return 解析后的Date对象
* @throws ParseException 解析异常
*/
public static Date parse(String dateString) throws ParseException {
return sdf.parse(dateString);
}
}
/**
* 线程安全的日期格式化器
* 使用ThreadLocal为每个线程提供独立的SimpleDateFormat实例
*/
public static class SafeDateFormatter {
private static final ThreadLocal<SimpleDateFormat> sdfThreadLocal = ThreadLocal
.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
/**
* 格式化日期
*
* @param date 要格式化的日期
* @return 格式化后的字符串
*/
public static String format(Date date) {
SimpleDateFormat formatter = sdfThreadLocal.get();
return formatter.format(date);
}
/**
* 解析日期字符串
*
* @param dateString 日期字符串
* @return 解析后的Date对象
* @throws ParseException 解析异常
*/
public static Date parse(String dateString) throws ParseException {
SimpleDateFormat formatter = sdfThreadLocal.get();
return formatter.parse(dateString);
}
/**
* 清理当前线程的ThreadLocal变量,防止内存泄漏
*/
public static void cleanup() {
sdfThreadLocal.remove();
}
}
public static void main(String[] args) throws InterruptedException {
System.out.println("--- 演示线程安全问题 (比较格式化后的字符串) ---");
demonstrateUnsafeUsage();
System.out.println("\n--- 演示使用ThreadLocal后的安全用法 (比较格式化后的字符串) ---");
demonstrateSafeUsage();
}
/**
* 演示不安全的日期格式化用法
* 创建多个线程并发使用共享的SimpleDateFormat实例
*
* @throws InterruptedException 线程中断异常
*/
private static void demonstrateUnsafeUsage() throws InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 100; i++) {
final int taskId = i;
executor.submit(() -> {
try {
// 使用一个固定的当前时间,确保 format 和 parse 操作基于同一时刻
long currentTimeMillis = System.currentTimeMillis();
Date originalDate = new Date(currentTimeMillis);
String formattedOriginal = UnsafeDateFormatter.format(originalDate);
Date parsedDate = UnsafeDateFormatter.parse(formattedOriginal);
String formattedParsed = UnsafeDateFormatter.format(parsedDate);
// 比较格式化后的字符串是否一致
if (!formattedOriginal.equals(formattedParsed)) {
System.out.println("Unsafe Task " + taskId + ": Mismatch detected! Original Formatted: "
+ formattedOriginal + ", Parsed & Formatted: " + formattedParsed);
}
} catch (ParseException e) {
System.out.println("Unsafe Task " + taskId + ": Parse Exception - " + e.getMessage());
e.printStackTrace();
}
});
}
executor.shutdown();
try {
if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}
System.out.println("Unsafe usage completed.");
}
/**
* 演示安全的日期格式化用法
* 使用ThreadLocal为每个线程提供独立的SimpleDateFormat实例
*
* @throws InterruptedException 线程中断异常
*/
private static void demonstrateSafeUsage() throws InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 100; i++) {
final int taskId = i;
executor.submit(() -> {
try {
// 使用一个固定的当前时间,确保 format 和 parse 操作基于同一时刻
long currentTimeMillis = System.currentTimeMillis();
Date originalDate = new Date(currentTimeMillis);
String formattedOriginal = SafeDateFormatter.format(originalDate);
Date parsedDate = SafeDateFormatter.parse(formattedOriginal);
String formattedParsed = SafeDateFormatter.format(parsedDate);
// 比较格式化后的字符串是否一致
if (!formattedOriginal.equals(formattedParsed)) {
System.out.println("Safe Task " + taskId + ": Mismatch detected! Original Formatted: "
+ formattedOriginal + ", Parsed & Formatted: " + formattedParsed);
}
} catch (ParseException e) {
System.out.println("Safe Task " + taskId + ": Parse Exception - " + e.getMessage());
e.printStackTrace();
} finally {
// 清理当前线程的 ThreadLocal 变量,防止内存泄漏
SafeDateFormatter.cleanup();
}
});
}
executor.shutdown();
try {
if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}
System.out.println("Safe usage completed.");
}
}

被折叠的 条评论
为什么被折叠?



