本文主要是介绍ThreadLocal及InheritableThreadLocal基本原理及注意项,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
背景
同事在使用ThreadLocal时,需要把主线程中的ThreadLocal值传输到子线程(线程池维护)中,故使用了InheritableThreadLocal作为传输。后发现,主线程执行ThreadLocal.remove()后,子线程中的ThreadLocal并不会被remove(),导致线程池中维护的ThreadLocal存储的值一直不变。于是深入进行了研究。
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时候又是怎么取出来的。
Set方法
首先先看到 ThreadLocal的set源码:
public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(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并不是构建出来就创建了的,而是需要用到的时候再去创建,这种懒加载的思想其实可以用到我们日常开发中,对于在某些判断分支下不需要创建的数组或对象不要提前进行创建。
Get方法
接下来看到 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);elsecreateMap(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原理
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源码
再来看 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);}
}
大致看下这几个方法:
- childValue(): 直接返回传入的parentValue,这个看起来有点不明所以,之后再来展开
- getMap(Thread t): 返回了t.inheritableThreadLocals,结合之前的get,set源码的阅读,也就算说如果你是一个InheritableThreadLocal对象,那么它会从线程的inheritableThreadLocals来执行get,set操作
- createMap(Thread t, T firstValue): 创建一个ThreadLocalMap,并赋值给线程的inheritableThreadLocals
从上述getMap、createMap方法,结合我们之前看到get,set源码,那就可以理解到“InheritableThreadLocal 的值会放到 Thread的 inheritableThreadLocals 属性中进行操作”这句话的意义。
childValue与parentMap浅拷贝
那么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方法
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)
注意项
- ThreadLocal与当前线程绑定,如果不用的时候需及时进行remove,否则会导致内存泄露。
- InheritableThreadLocal 中是将父线程中的 inheritableThreadLocals 浅拷贝到到子线程中,代码上看就算不使用也会拷贝。(如理解有误请指正)
- 父子线程的 inheritableThreadLocals 并不共享,如果你在父线程中执行了remove,子线程中不会受影响,依旧可以get出来。如果有使用到判空get值做判空处理,需注意。
- inheritableThreadLocals 只会在初始化时进行拷贝,如果使用线程池需要注意。可看下面例子。
ThreadLocal<String> t = new InheritableThreadLocal<>();t.set("test");ExecutorService executorService = Executors.newSingleThreadExecutor();executorService.execute(() -> {System.out.println(t.get());t.remove();});// 确保上面代码执行完毕Thread.sleep(1000);// 再次执行,在同一线程中,没有新建线程,所以不会进行重新拷贝,输出为nullexecutorService.execute(() -> {System.out.println(t.get());});
输出为:
这篇关于ThreadLocal及InheritableThreadLocal基本原理及注意项的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!