1 前言
SimpleDateFormat定义为static时,多线程环境下共用此对象是线程不安全的。如下采用双重检测锁,为每个线程单独创建SimpleDateFormat对象,即可避免此问题。
2 使用
单例模式常见双重检测锁的使用:
package com.xiaoxu.crawler.utils;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
/**
* @author xiaoxu
* @date 2022-11-22 0:00
* crawlerJ:com.xiaoxu.crawler.utils.DateUtils
*/
public class DateUtils {
private static final Object lockObj = new Object();
private static volatile Map<String, ThreadLocal<SimpleDateFormat>> timeLocalMaps = new HashMap<>();
private static SimpleDateFormat getSDF(String pattern){
AssertUtils.assertNonEmpty(pattern, "time pattern should not be null or empty.");
ThreadLocal<SimpleDateFormat> local = timeLocalMaps.get(pattern);
/* 双重检测锁,为timeLocalMaps增加volatile标识,
* 禁止指令重排 */
if(null == local){
synchronized (lockObj){
local = timeLocalMaps.get(pattern);
if(null == local){
local = ThreadLocal.withInitial(()->new SimpleDateFormat(pattern));
timeLocalMaps.put(pattern, local);
}
}
}
return local.get();
}
public static void main(String[] args) {
long start = System.currentTimeMillis();
CountDownLatch countDownLatch = new CountDownLatch(100000);
for (int i = 0; i < 1000; i++) {
for(int j = 0; j<100; j++) {
new Thread(() -> {
try {
// 每个子线程单独创建sdf变量
SimpleDateFormat sdf = DateUtils.getSDF("yyyy-MM-dd");
Date parse = null;
try {
parse = sdf.parse("2022-11-10");
} catch (Exception e) {
System.out.println("错误!!!!");
throw new RuntimeException(e);
}
System.out.println("日期是" + parse);
} finally {
countDownLatch.countDown();
}
}, "thread-" + j).start();
}
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
long end = System.currentTimeMillis();
System.out.println("总耗时:"+(end - start)+"ms");
}
}
package com.xiaoxu.crawler.utils;
import com.xiaoxu.crawler.excp.CrawlerForJException;
import org.apache.commons.lang3.ArrayUtils;
/**
* @author xiaoxu
* @date 2022-11-06 15:50
* crawlerJ:com.xiaoxu.crawler.utils.AssertUtils
*/
public class AssertUtils {
/* 校验为真 */
public static void assertTrue(boolean res, String errorMsg){
handlerError(res, errorMsg);
}
/* 校验非空 */
public static <T> void assertNonNull(T obj, String errorMsg){
handlerError(null != obj, errorMsg);
}
/* 校验非null非empty字符串 */
public static <T> void assertNonEmpty(String str, String errorMsg){
handlerError(null != str && !str.isEmpty(), errorMsg);
}
/* 校验非空Array */
public static <T> void assertNonEmptyArray(T[] array, String errorMsg){
handlerError(!ArrayUtils.isEmpty(array), errorMsg);
}
/* 统一异常处理 */
private static void handlerError(boolean flag, String message){
if(!flag){
/* 使用公共异常处理 */
throw new CrawlerForJException(message);
}
}
}
执行结果无误:
注意:因为加锁和释放锁,会自动将线程变量写回到主内存中的timeLocalMaps变量,实际上述timeLocalMaps是线程间可见的。只是单例模式下,避免指令重排多次创建对象,违背了单例的初衷,故而在单例模式下,双重检测锁常与volatile(禁止指令重排)一同使用。