- 为什么会有ThreadLocal?
提供了一个变量在线程间隔离而在类和方法间共享的一种方法。
虽然我们可以通过其他方法来实现相同的功能,但是使用ThreadLocal会更加的简洁和优雅。
ThreadLocal是如何实现的?
先看结构图,一步一步解释。
- 大致流程
每个ThreadLocal实例 set 赋值的时候,会先获取到当前线程的ThreadLocals(基于弱引用自定义实现的一个Map),然后把弱引用key指向当前ThreadLocal实例。
- ThreadLocals来源于哪里?
追踪发现,来源于当前线程:
- 为什么使用弱引用
如上面的结构图,正常情况下我们new一个ThreadLocal的时候就有一个强引用指向当前变量,如果数组里的key是强引用,当我们set赋值后,又有一个引用指向当前变量,那么我们就无法通过下面的代码回收ThreadLocal:
ThreadLocal<Object> current = new ThreadLocal<>();
current.set(“str”)
current = null;
- 那么使用弱引用就可以避免内存泄露吗?
不一定,当我们把current设置为null后,下一次GC会把current内存回收,但是ThreadLocals里面存储的value并不能回收,还是会造成内存泄露,好在ThreadLocal的get和set,remove可以自动去把JVM已经GC掉的ThreadLocal对应的Entry给清理掉,这一点比强引用好,但是也并不保险。
- 那么我们该如何使用ThreadLocal呢?
使用示例
Random随机数
Random在获取随机数的时候,会先通过CAS尝试修改seed,所以多线程下是不安全的。
解决方法:自己封装或者使用JDK8提供的ThreadLocalRandom。
SimpleDateFormat
SimpleDateFormat是线程不安全的,因为parse和format方法会频繁修改父类的calendar。
使用ThreadLocal封装的示例类:
/**
* 时间工具类
*/
public class TimeUtil {
public static final String YEAR_MONTH_DAY_SECOND = "yyyy-MM-dd HH:mm:ss";
public static final String YEAR_MONTH_DAY_SECOND2 = "yyyy/MM/dd HH:mm:ss";
public static final String YEAR_MONTH_DAY_SECOND3 = "yyyy年MM月dd日 HH时mm分ss秒";
public static final String YEAR_MONTH_DAY = "yyyy-MM-dd";
public static final String YEAR_MONTH_DAY2 = "yyyy年MM月dd日";
/**
* 采用 ThreadLocal 避免 SimpleDateFormat 非线程安全的问题
* <p>
* Key —— 时间格式
* Value —— 解析特定时间格式的 SimpleDateFormat
*/
private static ThreadLocal<Map<String, SimpleDateFormat>> sThreadLocal = new ThreadLocal<>();
/**
* 获取解析特定时间格式的 SimpleDateFormat
*
* @param pattern 时间格式
*/
private static SimpleDateFormat getDateFormat(String pattern) {
Map<String, SimpleDateFormat> strDateFormatMap = sThreadLocal.get();
if (strDateFormatMap == null) {
strDateFormatMap = new HashMap<>();
}
SimpleDateFormat simpleDateFormat = strDateFormatMap.get(pattern);
if (simpleDateFormat == null) {
simpleDateFormat = new SimpleDateFormat(pattern, Locale.getDefault());
strDateFormatMap.put(pattern, simpleDateFormat);
sThreadLocal.put(strDateFormatMap);
}
return simpleDateFormat;
}
/**
* 时间格式化
*
* @param date:要格式化的时间
* @param pattern:要格式化的类型
*/
public static String formatDate(Date date, String pattern) {
if (date == null || pattern == null) {
return null;
}
return getDateFormat(pattern).format(date);
}
}