聊聊ThreadLocal

ThreadLocal

维持线程封闭性的一种规范方法是使用ThreadLocal,这个类能使线程中的某个值与保存值的对象关联起来。提供了getset方法等访问接口和方法,这些方法为每个使用该变量的的线程都存有一份独立的副本,因此get总是返回由当前执行线程在调用set时设置的最新值。

官方解释如下:

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).

Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal instance is accessible; after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other references to these copies exist).

就是说ThreadLocal提供了线程本地变量,简单点说就是为每个线程都初始化了一份完全独立的变量副本供线程使用。ThreadLocal通常用private static来修饰。当一个线程结束时,它所使用的所有ThreadLocal相对的实例副本都被回收。

常见用法

ThreadLocal对象通常用于防止对可变的单实例变量(Singleton)或全局变量进行共享。

项目中经常会用到时间日期转换,所以新建DateFormatUtils工具类,获取我们会有如下写法:

1
2
3
4
5
private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

public static Date parse(String strDate) throws ParseException {
return sdf.parse(strDate);
}

这种常见写法首先解决了应用反复创建SimpleDateFormat对象所带来的系统损耗,但是又引来了一个非常重要的问题–并发问题。如果我们在并发环境下使用该方法:

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + ":" + DateFormatUtils.parse("2013-05-24 06:02:20"));
} catch (ParseException e) {
e.printStackTrace();
}
}).start();
}
}

系统运行情况如图所示:

由此我们发现,该方法在多线程环境下会有线程安全的问题。

解决并发问题的方式有很多,如synchronized,这里使用ThreadLocal来解决此问题。

DateFormatUtils类改造如下:

1
2
3
4
5
6
7
8
9
10
11
private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>(){
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
};

public static Date parse(String strDate) throws ParseException {
DateFormat sdf = threadLocal.get();
return sdf.parse(strDate);
}

运行成功!

源码阅读分析

  • ThreadLocal 结构
  • ThreadThreadLocalThreadLocalMap 三者关系

可以发现每一个Thread中都有一个ThreadLocalMapThreadLocalMap可以看作是一个数组,数组元素是自定义的Entry类型,EntrykeyThreadLocal类型,valueObject类型。也就是说一个ThreadLocalMap可以有多个ThreadLocal