三流码奴的自我救赎

0%

理解ThreadLocal

ThreadLocal

ThreadLocal<T>可以认为是一个绑定到当前Thread的标签。
可以将一个value绑定到这个标签,形成一个Entry存储到由Thread维护的ThreadLocalMap中。

ThreadLocalMap

ThreadLocalMap是ThreadLocal的一个静态内部类,其实就是一个重新实现过的HashMap,相对于HashMap有如下特点:

  1. 定义一个继承自WeakReferener<ThreadLocal<?>>的静态内部类Entry
1
2
3
4
5
6
7
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}

作用类似于HashMap中的Entry,只不过这个Entry的key是自身的外部类ThreadLocal<T>value则是ThreadLocal<T>的泛型参数。这就是前面为什么说ThreadLocal<T>其实是一个标签。
这个做法有一个非常精妙的地方,前面提到过ThreadLocalMap并不是由ThreadLocal维护的,而是由每一个Thread维护自己的ThreadLocalMap。也就是说当线程退出之后,对应的ThreadLocalMap将会被回收,里面存储的所有Entry也会被回收。
但需要注意的是,在这个设计中EntryThreadLocal<T>是弱引用,而value是强引用,意味着当ThreadLocal<T>的引用被销毁后,Entryvalue的引用依然存在,所以如果需要回收ThreadLocal对应的数据,则需要手动将value一并回收,否则会发生内存泄漏

  1. HashMap不同,ThreadLocalMap在处理哈希冲突的时候使用的是开放地址法,当位置被占用的时候会直接去找下一个位置,利用空间换取时间,效率要高于HashMap

ThreadLocal如何与Thread绑定

先来看ThreadLocal中的getset方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public T get() {
// 获取当前线程
Thread t = Thread.currentThread();
// 通过当前线程获取ThreadLocalMap
ThreadLocalMap map = getMap(t);

if (map != null) {
// 若ThreadLocalMap存在则尝试从中取出Entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
// 若entry不为空则将value强转成泛型返回
T result = (T)e.value;
return result;
}
}
//若ThreadLocalMap为空则执行初始化
return setInitialValue();
}

public void set(T value) {
// 获取当前线程
Thread t = Thread.currentThread();
// 通过当前线程获取ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null)
// ThreadLocalMap存在则直接以自己为key添加新值
map.set(this, value);
else
// ThreadLocalMap不存在则进行创建
createMap(t, value);
}

从源码中可以看出,ThreadLocal在操作之前都会通过Thread类获取当前线程,从而保证下一步获取到的ThreadLocalMap永远是在当前线程中维护的,从而将自己对应的数据绑定到线程。