同事在使用ThreadLocal时,需要把主线程中的ThreadLocal值传输到子线程(线程池维护)中,故使用了InheritableThreadLocal作为传输。后发现,主线程执行ThreadLocal.remove()后,子线程中的ThreadLocal并不会被remove(),导致线程池中维护的ThreadLocal存储的值一直不变。于是深入进行了研究。
要讲清楚InheritableThreadLocal原理,首先要知道ThreadLocal是如何做到在线程中存储变量的,因为两者本质上是一个东西,只是InheritableThreadLocal多了一个子线程可继承的功能(部分方法实现不同)。
直觉上理解可能认为ThreadLocal是一个Map,保存着Thread和存储变量的一个映射,就像这样Map<Thread, V>,但实际不是。
从Thread类的源码中,我们可以看到这样一个threadLocals变量
- public
- class Thread implements Runnable {
- ...
- /* ThreadLocal values pertaining to this thread. This map is maintained
- * by the ThreadLocal class. */
- ThreadLocal.ThreadLocalMap threadLocals = null;
- ...
- }
ThreadLocalMap中里面有一个Entry类,类似我们熟知的HashMap中,也有一个Entry类一样,ThreadLocalMap中的Entry存的则是<ThreadLocal<?>, value>,保存着ThreadLocal和value的映射。
- static class ThreadLocalMap {
- ...
- static class Entry extends WeakReference<ThreadLocal<?>> {
-
- Object value;
-
- Entry(ThreadLocal<?> k, Object v) {
- super(k);
- value = v;
- }
- }
- ...
-
- private Entry[] table;
- ...
- }
同时ThreadLocalMap中,有一个Entry数组table作为存储。那么我们可以画出实际上Thread和ThreadLocal的关系实际上是这样的。
也就是一个Thread可以拥有多个ThreadLocal,通过ThreadLocalMap来进行存储,这就是ThreadLocal和Thread之间的关系。
顺带补充一下,我们知道HashMap底层结构是数组+链表,用Key值的HashCode来计算数组中存放的index,用链表来解决哈希冲突,即链地址法。
而ThreadLocalMap类似,利用ThreadLocal的hashcode来计算数组中存放的index,但是解决哈希冲突方法是,如果计算出来的index已存放了元素,那么index+1,越界时则重新回到0。当然这样最终会导致无地址可以用,再一直这样循环查找会导致死循环,所以当数组中元素超出一定阈值(threshold)时,就会进行扩容,此处便不再进行展开。
前面讲了ThreadLocal和Thread的联系,那么调用ThreadLocal.set的时候,是如何做到保存到对应线程中的,调用get时候又是怎么取出来的。
首先先看到 ThreadLocal的set源码:
-
- public void set(T value) {
- Thread t = Thread.currentThread();
- ThreadLocalMap map = getMap(t);
- if (map != null)
- map.set(this, value);
- else
- createMap(t, value);
- }
-
-
- ThreadLocalMap getMap(Thread t) {
- return t.threadLocals;
- }
-
- void createMap(Thread t, T firstValue) {
- t.threadLocals = new ThreadLocalMap(this, firstValue);
- }
当调用set时,首先会获取到当前执行线程,并获取当前线程的 threadLocals 属性,即上文我们着重讲的ThreadLocalMap,若不存在则创建,然后以当前的ThreadLocal对象作为key,存入ThreadLocalMap中。大致流程图如下:
这里我们也可以知道Thread中的ThreadLocalMap并不是构建出来就创建了的,而是需要用到的时候再去创建,这种懒加载的思想其实可以用到我们日常开发中,对于在某些判断分支下不需要创建的数组或对象不要提前进行创建。
接下来看到 ThreadLocal的get源码:
- 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();
- }
-
-
- private T setInitialValue() {
- T value = initialValue();
- Thread t = Thread.currentThread();
- ThreadLocalMap map = getMap(t);
- if (map != null)
- map.set(this, value);
- else
- createMap(t, value);
- return value;
- }
-
-
- ThreadLocalMap getMap(Thread t) {
- return t.threadLocals;
- }
-
- void createMap(Thread t, T firstValue) {
- t.threadLocals = new ThreadLocalMap(this, firstValue);
- }
当调用get时,首先会获取当前执行线程,从线程中获取ThreadLocalMap,再从ThreadLocalMap中获取当前ThreadLocal对应的值。当获取不到ThreadLocalMap,或者当前ThreadLocal没有值时,则进行初始化,若当前线程ThreadLocalMap不存在,则创建当前线程的ThreadLocalMap,并存放入初始值(默认为null),若已存在,则存入当前ThreadLocal和初始值至ThreadLocalMap中。大致流程图如下:
图上和代码上的判定分支流程略有差异,但意思一致,initialValue() 默认return null,所以这里用null表达,可以对照源码理解。
至此,对ThreadLocal的基本原理我们有了大致上的理解,还有常用remove方法及其他建议自行阅读源码。这里仅对自己的研究做一个总结和分享。
那么接下来,就可以研究 InheritableThreadLocal 是如何实现可继承性的。
InheritableThreadLocal作用是:将在父线程中放入ThreadLocal的值,在子线程中继承使用。正常我们使用ThreadLocal是无法做到这一步的,我们可以写一段程序实验一下。
这里我们看到输出了null,当然,这是必然的。ThreadLocal设计就是为了保存线程各自的本地变量互不干扰,如果两个线程能访问到同一个,那就变得线程不安全,也违背了它的设计。
然后我们将ThreadLocal换成InheritableThreadLocal,再试试。
可以看到获取ThreadLocal中的值成功了。那这样是否会导致线程不安全呢,还能叫线程本地变量么,答案是可以的。接下来看下InheritableThreadLocal的实现,我们就可以理解了。
首先看Thread类中,可以看到有两个ThreadLocalMap,threadLocals 和inheritableThreadLocals。
-
- public
- class Thread implements Runnable {
- ...
-
- /* ThreadLocal values pertaining to this thread. This map is maintained
- * by the ThreadLocal class. */
- ThreadLocal.ThreadLocalMap threadLocals = null;
-
- /*
- * InheritableThreadLocal values pertaining to this thread. This map is
- * maintained by the InheritableThreadLocal class.
- */
- ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
- ...
- }
从注释可以看到,简单来说,就是 ThreadLocal 的值放到 threadLocals 里,InheritableThreadLocal 的值放到 inheritableThreadLocals 里。
那么我们首先可以有一个概念——InheritableThreadLocal 的值会放到 Thread的 inheritableThreadLocals 属性中进行操作。
再来看 InheritableThreadLocal 的源码,可以看到它继承了 ThreadLocal,并重写了其中几个方法。它的可继承能力,就在它重写的几个方法中。
- public class InheritableThreadLocal<T> extends ThreadLocal<T> {
-
- protected T childValue(T parentValue) {
- return parentValue;
- }
-
-
- ThreadLocalMap getMap(Thread t) {
- return t.inheritableThreadLocals;
- }
-
-
- void createMap(Thread t, T firstValue) {
- t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
- }
- }
大致看下这几个方法:
从上述getMap、createMap方法,结合我们之前看到get,set源码,那就可以理解到“InheritableThreadLocal 的值会放到 Thread的 inheritableThreadLocals 属性中进行操作”这句话的意义。
那么childValue()是干嘛的呢?看下哪里会调到它,就会发现一片新的天地。顺着childValue点开调用它的地方,可以看到是一个私有构造函数。
- private ThreadLocalMap(ThreadLocalMap parentMap) {
- Entry[] parentTable = parentMap.table;
- int len = parentTable.length;
- setThreshold(len);
- table = new Entry[len];
-
- for (int j = 0; j < len; j++) {
- Entry e = parentTable[j];
- if (e != null) {
- @SuppressWarnings("unchecked")
- ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
- if (key != null) {
- Object value = key.childValue(e.value);
- Entry c = new Entry(key, value);
- int h = key.threadLocalHashCode & (len - 1);
- while (table[h] != null)
- h = nextIndex(h, len);
- table[h] = c;
- size++;
- }
- }
- }
- }
大致浏览一下,可以看到,它作用是把传入的 parentMap 浅拷贝到当前子线程的ThreadLocalMap中。
那么再看下这个私有构造函数被谁调用,依次跟下来可以看到这样一个调用链。
> new Thread(...)
> init(...)
>ThreadLocal.createInheritedMap(parent.inheritableThreadLocals)
>ThreadLocalMap(parentMap)
init方法会在构建Thread的时候执行,在 init 方法中,我们可以看到这么一段:
- private void init(ThreadGroup g, Runnable target, String name,
- long stackSize, AccessControlContext acc,
- boolean inheritThreadLocals) {
- ...
- Thread parent = currentThread();
- ...
- if (inheritThreadLocals && parent.inheritableThreadLocals != null)
- this.inheritableThreadLocals =
- ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
- ...
- }
当if条件不为 false 时,那么就会触发浅拷贝,将父线程中inheritableThreadLocals中的值复制到子线程的inheritableThreadLocals中。
这里父线程 parent = currentThread(); 如果不理解的话,想一下,我们是从当前线程再创建线程,那么当前线程其实就是父线程了。
这里我们再来看两个条件 inheritThreadLocals 和 parent.inheritableThreadLocals != null
首先看 inheritThreadLocals,可以看到它是由方法入参来的,那么就看下它被调的时候,传了啥。
这里看到有两个地方调了它,有acc(AccessControlContext)传false,没有的时候传true,这里 AccessControlContext 是什么可以自行研究,正常new Thread()的时候,默认传true。
搞定完 inheritThreadLocals,再来看 parent.inheritableThreadLocals != null,这个条件比较好理解。InheritableThreadLocal类重写了 createMap 和 getMap 方法,将所有操作指向了 Thread.inheritableThreadLocals。
那么只要我们在父线程的 InheritableThreadLocal,调用get或set了,那么 parent.inheritableThreadLocals != null 就成立。
上述两个条件 inheritThreadLocals && parent.inheritableThreadLocals != null 成立后,那就会触发浅拷贝到子线程的 inheritableThreadLocals 中了,在构建完成后,子线程便可以读取到父线程中ThreadLocal中的值。
(注:父子线程中的 inheritableThreadLocals 并不共享,依旧保持ThreadLocal特性,仅仅是将父线程 inheritableThreadLocals 的元素浅拷贝到子线程中,子线程有自己的inheritableThreadLocals)