前言
顾名思义,ThreadLocal 用于保存每个线程的本地变量,每个线程都有一个 threadLocals 成员:
1 | public class Thread implements Runnable { |
1 | public class ThreadLocal<T> { |
可以看到,ThreadLocalMap 存储了 ThreadLocal 与 Object 的键值对 Entry,而 Entry 中的 key 则是一种弱引用。一个对象,如果只有通过弱引用才能到达,则称为弱可达的。弱可达的对象,是可以被垃圾收集器进行回收的。
只得注意的是,虽然threadLocals属于每个thread对象,但是却是通过ThreadLocal来管理的。
## 导航
set
在调用 ThreadLocal.set(T value)时,将会将<threadLocal,value>键值对保存至当前线程的 threadLocals 中,从而成为当前线程的独有变量。
set 方法非常简单。
1 | public void set(T value) { |
注意到,threadLocals 并不会随着 Thread 的初始化而被初始化,而是在确实需要保存变量的时候才会被初始化。
get
get 方法也很简单,而get方法中也可能进行初始化:
1 | public T get() { |
ThreadLocalMap
Thread、ThreadLocal与ThreadLocalMap
通过以上 ThreadLocal 的 get/set 方法,我们知道虽然调用的是 ThreadLocal 里的方法,受影响的却是 Thread 中的 threadLocals。ThreadLocalMap 是真正连接 Thread 与 ThreadLocal 的类:thread拥有threadLocalMap,threadLocalMap以threadLocal为key值。
那为什么不直接在Thread中增加成员变量来存储独有变量呢?笔者认为,从获取变量的效率来看,散列表优于链表。而釆用散列表,就必须寻找一个合适的key绑定value。object.hashcode无疑是key键的优选。所以自然而然的,我们需要寻求一个对象来充当key。java中的这个对象,就是ThreadLocal了,而map,自然是ThreadLocalMap。
因此,ThreadLocal除了暴露对外接口,另一个重要作用是绑定value,提供高效查询。
ThreadLocalMap是一个定制的 map。在 ThreadLocalMap 中,key 虽然是 threadLocal,但是,get/set 时,用于判断的并不是直接继承于 Object 类的 hashcode,而是采用自 0 递增的 hashcode,保证了散列效果。
1 | private final int threadLocalHashCode = nextHashCode(); |
ThreadLocalMap.set
产生散列冲突的时候,ThreadLocalMap 采取线性探测法处理:
1 | private void set(ThreadLocal<?> key, Object value) { |
ThreadLocalMap.get
和 set 方法不同,get 只处理 fast path——由 key.hash 直接得到的 table[i]。如果通过 fast path 没有获得正确的 entry,则线性探测着清除对应下标的 entry 直至遇上 entry==null,之后返回 null。
1 | private Entry getEntry(ThreadLocal<?> key) { |
弱引用处理
由上文可知,ThreadLocalMap 为了弱引用做了额外的处理。
1 | // 从i开始往后扫描,介于不扫描(快速但会遗留垃圾)和完全扫描(线性时间)之间,对数时间 |