源码讲解ThreadLocal父子线程通信问题(图+文+源码)

1 缘起

在复习ThreadLocal相关应用的知识,
有一个老生常谈的问题:父子线程通信,
起初,对于父子线程通信,仅了解ThreadLocal无法通过子线程获取线程数据,
并不了解为什么会这样?以及为什么InheritableThreadLocal可以实现父子线程通信?
也是机缘巧合,在先复习的Thread相关属性过程中,发现有两个ThreadLocal.ThreadLocalMap类型的属性:
threadLocals和inheritableThreadLocals,用于存储线程的ThreadLocal相关数据,
结合ThreadLocal和InheritableThreadLocal初始化过程,了解到,分别用户存储数据,
但是,这还不是父子线程通信的根源,接着分析Thread相关,发现,Thread初始化过程,
有通过父线程初始化inheritableThreadLocals属性,定位到根源了,结合ThreadLocal和InheritableThreadLocal的set和get,
最终,全面了解ThreadLocal父子线程通信,
分享如下,帮助读者轻松应对知识交流与考核。

ThreadLocal测试文章

https://blog.csdn.net/Xin_101/article/details/115706182

2 源码分析

2.1 结构

涉及ThreadLocal父子线程通信的核心类有三个:Thread、ThreadLocal和InheritableThreadLocal,
UML关系图如下图所示,InheritableThreadLocal继承ThreadLocal,
ThreadLocal内部类聚合到Thread中,作为属性。
(1)Thread类涉及的方法为init方法,初始化线程,该过程会通过父线程初始化ineritableThreadLocals属性。
(2))ThreadLocal涉及的方法为set、get、createMap,其中,set即存储值到ThreadLocal,get从ThreadLocal获取值,
createMap则是构建ThreadLocalMap,用于存储和获取值;
(3)InheritableThreadLocal继承ThreadLocal,重写了三个方法:childValue、createMap和getMap,其中,
createMap初始化的属性为inheritableThreadLocals,实现从父线程获取inheritableThreadLocals,实现子线程获取父线程数据。
在这里插入图片描述

2.2 存储值:set

ThreadLocal通过set方法将值存储到ThreadLocalMap,
ThreadLocal赋值方法set如下图所示,
由源码可知,先获取当前线程,然后获取线程的ThreadLocalMap,
其中,ThreadLocal获取的ThreadLocalMap为线程的threadLocals属性,
如果获取的ThreadLocalMap为空,则初始化ThreadLocalMap,ThreadLocal初始化的为threadLocals。
位置:java.lang.ThreadLocal#set
在这里插入图片描述
ThreadLocal初始化ThreadLocalMap源码如下图所示,
由源码可知,ThreadLocal初始化的TheadLocalMap为线程的threadLocals属性。
位置:java.lang.ThreadLocal#createMap
在这里插入图片描述
InheritableThreadLocal初始化ThreadLocalMap的源码如下图所示,
由源码可知,InheritableThreadLocal初始化的属性为线程的inheritableThreadLocals,
这为后续从父线程获取inheritableThreadLocals,初始化子线程的inheritableThreadLocals做了准备工作。
java.lang.InheritableThreadLocal#createMap
在这里插入图片描述

2.3 线程初始化:init

根据线程生命周期,可知,线程有一个初始化过程,
线程初始化源码如下图所示,由源码可知,
线程初始化会获取当前线程,即父线程(是个本地方法),
通过父线程获取inheritableThreadLocals,用于初始化子线程的inheritableThreadLocals属性,
初始化子线程的过程为:ThreadLocal.createInheritedMap,
从而,实现父子线程通信,子线程可以获取父线程中存储的数据。
位置:java.lang.Thread#init(java.lang.ThreadGroup, java.lang.Runnable, java.lang.String, long, java.security.AccessControlContext, boolean)

在这里插入图片描述
createInheritedMap源码如下图所示,
由源码可知,createInheritedMap使用父线程的ThreadLocalMap创建新的ThreadLocalMap,
并将该ThreadLocalMap赋给子线程的inheritableThreadLocals,
此时子线程就拥有了父线程的ThreadLcoalMap,继承了该ThreadLocaMap存储的值,
自然可以获取父线程中存储的值。
位置:java.lang.ThreadLocal#createInheritedMap
在这里插入图片描述

2.4 获取值:get

ThreadLoacal通过get方法获取存储在ThreadLocalMap中存储的值,
源码如下图所示,由源码可知,从ThreadLocal获取值,先获取当前线程,
然后获取TheadLocalMap,这里就出现了父子线程通信的分水岭,
(1)ThreadLocal获取TheadLocalMap是从线程的threadLocals属性获取,
而子线程初始化时并没有对自身的threadLocals做任何操作,所以获取的ThreadLocalMap自然是null,
最终返回的值为null;
(2)InheritableThreadLocal获取的ThreadLocalMap是从线程的inheritableThreadLocals属性获取,
而子线程在初始化时,根据父线程inheritableThreadLocals将自身的inheritableThreadLocals同时进行了初始化,
这样,子线程,就从父线程继承了inheritableThreadLocals,获取了相关数据,
在子线程中get数据,自然有值。
这里在补充一下,set过程中,ThreadLocal初始化线程的threadLocals属性,InheritableThreadLocal初始化线程的inheritableThreadLocals属性。
位置:java.lang.ThreadLocal#get
在这里插入图片描述
ThreadLocal获取ThreadLocalMap源码如下图所示,
由源码可知,ThreadLocal从线程的threadLocals属性获取,
而线程初始化过程,并没有通过父线程初始化threadLocals。
位置:java.lang.ThreadLocal#getMap
在这里插入图片描述

InheritableThreadLocal获取ThreadLocalMap源码如下图所示,
由源码可知,InheritableThreadLocal获取ThreadLocalMap从线程的inheritableThreadLocals属性获取,
而,子线程在初始化过程中,会使用父线程的inheritableThreadLocals属性重新初始化子线程的inheritableThreadLocals。
java.lang.InheritableThreadLocal#getMap

在这里插入图片描述

2.5 图解

为方便理解,图解TheadLocal和InheritableThreadLocal父子线程通信问题。

2.5.1 ThreadLocal

TheadLocal父子线程通信图解如下图所示,父线程和子线程间毫无交流,
造成子线程无法获取父线程信息。
在这里插入图片描述

2.5.2 InheritableThreadLocal

TheadLocal父子线程通信图解如下图所示,父线程和子线程通过继承inheritableThreadLocals属性,
实现父子线程通信。
在这里插入图片描述

3 小结

(1)ThreadLocal无法获取父线程数据,ThreadLocal获取数据时直接通过线程的threadLcoals属性获取ThreadLocalMap,由于Thread初始化时没有针对threadLocals进行操作,在子线程中,无法获取父线程的数据;
(2)InheritableThreadLocal可以获取父线程数据,InheritableThreadLocal获取数据时通过线程的inheritableThreadLcoals属性获取ThreadLocalMap,由于Thread初始化时通过父线程初始化了inheritableThreadLocals,所以在子线程中,可以正常获取父线程的数据;
(3)核心在于Thread的初始化过程,java.lang.Thread#init(java.lang.ThreadGroup, java.lang.Runnable, java.lang.String, long, java.security.AccessControlContext, boolean)方法初始化时针对inheritableThreadLocals做了初始化,解决了父子线程通信问题;
(4)ThreadLocal在获取值过程中,首先要获取当前线程,也是导致无法父子通信的一个原因,虽然保证了数据的隔离性。

猜你喜欢

转载自blog.csdn.net/Xin_101/article/details/128545506