ThreadLocal杂谈

ThreadLocal 是为多线程服务的。本质上ThreadLocal是一个关于创建线程局部变量的类。
通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。而使用ThreadLocal创建的变量只能被当前线程访问,其他线程则无法访问和修改。举个例子, 服务器同时被100个用户访问,访问的时候需要对用户信息进行操作,这里threadLocal就可以用来对这100个用户信息进行thread级别的隔离。

ThreadLocal是如何做到变量只能被当前线程访问呢?

首先需要声明的是thread local是通过给thread创建变量副本的方式来存储变量的。这些需要被线程使用到的变量就是存储在thread对象的threadLocals属性上。

这里又要提到ThreadLocalMap,这个是threadlocals的类型。从这个角度来看ThreadLocal类更像是一个util类,主要的职责就是帮助thread管理threadLocals属性。

变量存储

利用threadlocal对象 set 变量到thread上时调用的set方法源码是通过获取当前thread上绑定的threadlocals属性的值来进行操作,最终所有的值真正存储的位置其实都在thread本身与threadlocal对象并无直接关系。

1
2
3
4
5
6
7
8
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}

再来看get方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}

这里的设计就有点意思了,首先先从thread上拿到了threadlocals属性,这是一个ThreadLocalMap类型的值,也是数据真实的存储位置。thread所关联的所有threadlocal其实都存在这个map的entry里,也就是说不管这个thread里持有了多少个threadlocal实例,变量副本存储的地方有且只有一个map的entry。

每个Thread 维护一个 ThreadLocalMap 映射表,这个映射表的 key 是 ThreadLocal 实例本身,value 是真正需要存储的 Object。
也就是说 ThreadLocal 本身并不存储值,它只是作为一个 key 来让线程从 ThreadLocalMap 获取 value。值得注意的是图中的虚线,表示 ThreadLocalMap 是使用 ThreadLocal 的弱引用作为 Key 的,弱引用的对象在 GC 时会被回收。

threadlocal的存在是为了帮助thread进行threadlocals属性的关于制定key(threadllocal其实就是threadlocalMap的key)的赋值和取值。多线程之间的threadlocal实例本身其实是共享的,所以使用的时候一般定义成static变量,达到thread间共享实例的目的。