马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
InheritableThreadLocal相比ThreadLocal多一个本领:在创建子线程Thread时,子线程Thread会自动继承父线程的InheritableThreadLocal信息到子线程中,进而实如今在子线程获取父线程的InheritableThreadLocal值的目标。
关于ThreadLocal详细内容,可以看这篇文章:史上最全ThreadLocal 详解
和 ThreadLocal 的区别
举个简朴的栗子对比下InheritableThreadLocal和ThreadLocal:- public class InheritableThreadLocalTest {
- private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();
- private static final InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
- public static void main(String[] args) {
- testThreadLocal();
- testInheritableThreadLocal();
- }
- /** * threadLocal测试 */
- public static void testThreadLocal() {
- // 在主线程中设置值到threadLocal
- threadLocal.set("我是父线程threadLocal的值");
- // 创建一个新线程并启动
- new Thread(() -> {
- // 在子线程里面无法获取到父线程设置的threadLocal,结果为null
- System.out.println("从子线程获取到threadLocal的值: " + threadLocal.get()); }
- ).start();
- }
-
- /** * inheritableThreadLocal测试 */
- public static void testInheritableThreadLocal() {
- // 在主线程中设置一个值到inheritableThreadLocal
- inheritableThreadLocal.set("我是父线程inheritableThreadLocal的值");
- // 创建一个新线程并启动
- new Thread(() -> {
- // 在子线程里面可以自动获取到父线程设置的inheritableThreadLocal
- System.out.println("从子线程获取到inheritableThreadLocal的值: " + inheritableThreadLocal.get());
- }).start();
- }
- }
复制代码 实验效果:- 从子线程获取到threadLocal的值:null
- 从子线程获取到inheritableThreadLocal的值:我是父线程inheritableThreadLocal的值
复制代码 可以看到子线程中可以获取到父线程设置的inheritableThreadLocal值,但不能获取到父线程设置的threadLocal值
实现原理
InheritableThreadLocal 的实现原理相称精妙,它通过在创建子线程的瞬间,“复制”父线程的线程局部变量,从而实现了数据从父线程到子线程的一次性、创建时的转达 。
其焦点工作原理可以清晰地通过以下序列图展示,它描绘了当父线程创建一个子线程时,数据是怎样被转达的:
sequenceDiagram participant Parent as 父线程 participant Thread as Thread构造方法 participant ITL as InheritableThreadLocal participant ThMap as ThreadLocalMap participant Child as 子线程 Parent->>Thread: 创建 new Thread() Note over Parent,Thread: 关键步调:初始化 Thread->>Thread: 调用 init() 方法 Note over Thread,ITL: 查抄父线程的 inheritableThreadLocals Thread->>+ThMap: createInheritedMap(
parent.inheritableThreadLocals) ThMap->>ThMap: 新建一个ThreadLocalMap loop 遍历父线程Map中的每个Entry ThMap->>+ITL: 调用 key.childValue(parentValue) ITL-->>-ThMap: 返回子线程初始值
(默认返回父值,可重写) ThMap->>ThMap: 将 (key, value) 放入新Map end ThMap-->>-Thread: 返回新的ThreadLocalMap对象 Thread->>Child: 将新Map赋给子线程的
inheritableThreadLocals属性 Note over Child: 子线程拥有父线程变量的副本下面我们来详细拆解图中的关键环节。
焦点实现机制
- **数据结构根本:Thread类内部维护了两个 ThreadLocalMap范例的变量 :
- threadLocals:用于存储平凡 ThreadLocal设置的变量副本。
- inheritableThreadLocals:专门用于存储 InheritableThreadLocal设置的变量副本 。InheritableThreadLocal通过重写 getMap和 createMap方法,使其全部使用都针对 inheritableThreadLocals字段,从而与平凡 ThreadLocal分离开 。
- 继承触发时候:子线程的创建。继承活动发生在子线程被创建(即实验 new Thread())时。在 Thread类的 init方法中,假如判定必要继承(inheritThreadLocals参数为 true)且父线程(当火线程)的 inheritableThreadLocals不为 null,则会实验复制逻辑 。
- 复制过程的焦点:createInheritedMap。这是实现复制的焦点方法 。它会创建一个新的 ThreadLocalMap,并将父线程 inheritableThreadLocals中的全部条目遍历拷贝到新 Map 中。
- Key的复制:Key(即 InheritableThreadLocal对象自己)是直接复制的引用。
- Value的天生:Value 并非直接复制引用,而是通过调用 InheritableThreadLocal的 childValue(T parentValue)方法来天生子线程中的初始值。默认实现是直接返回父值(return parentValue;),这意味着对于对象范例,父子线程将共享同一个对象引用 。
关键特性与注意事项
- 创建时复制,后续独立:继承只发生一次,即在子线程对象创建的瞬间。以后,父线程和子线程对各自 InheritableThreadLocal变量的修改互不影响 。
- 在线程池中的范围性:这是 InheritableThreadLocal最必要鉴戒的题目。线程池中的线程是复用的,这些线程在初次创建时大概已经从某个父线程继承了值。但当它们被用于实验新的使命时,新的使命提交线程(逻辑上的“父线程”)与工作线程已无直接的创建关系,因此之前继承的值不会更新,这会导致数据繁芜(如用户A的使命拿到了用户B的信息)或内存走漏 。对于线程池场景,应思量使用阿里开源的 TransmittableThreadLocal (TTL) 。
- 浅拷贝与对象共享:由于 childValue方法默认是浅拷贝,假如存入的是可变对象(如 Map、List),父子线程现实持有的是同一个对象的引用。在一个线程中修改该对象的内部状态,会直接影响另一个线程 。若需隔离,可以重写 childValue方法实现深拷贝 。
- 内存走漏风险:与 ThreadLocal类似,假如线程长时间运行(如线程池中的焦点线程),而且未实时调用 remove方法清算,那么该线程的 inheritableThreadLocals会不绝持有值的强引用,导致无法被GC接纳。精良的实践是在使命实验完毕后自动调用 remove()
线程池中范围性
一样寻常来说,在真实的业务场景下,没人会直接 new Thread,而都是使用线程池的,因此InheritableThreadLocal在线程池中的使用范围性要额外注意
起首,我们先明白 InheritableThreadLocal的继承条件
- InheritableThreadLocal的继承只发生在 新线程被创建时(即 new Thread()并启动时)。在创建过程中,子线程会复制父线程的 InheritableThreadLocal值。
- 在线程池中,线程是预先创建或按需创建的,而且会被复用。因此,继承只会在线程池创建新线程时发生,而不会在复用现有线程时发生。
再看线程池创建新线程的条件,对于标准的 ThreadPoolExecutor,新线程的创建依照以下规则:
- 当火线程数 < 焦点线程数:当提交新使命时,假如当前运行的线程数小于焦点线程数,纵然有空闲线程,线程池也会创建新线程来处理处罚使命。此时,新线程会继承父线程(提交使命的线程)的 InheritableThreadLocal。
- 当火线程数 >= 焦点线程数 && 队列已满 && 线程数 < 最大线程数:当使命队列已满,且当火线程数小于最大线程数时,线程池会创建新线程来处理处罚使命。同样,新线程会继承父线程的 InheritableThreadLocal。
不会继承的场景
- 线程复用:当线程池中有空闲线程时(比方,当火线程数 >= 焦点线程数,但队列未满),使命会被分配给现有线程实验。此时,没有新线程创建,因此不会发生继承。现有线程的 InheritableThreadLocal值保持稳固(大概是之前使命设置的值),这大概导致数据繁芜(如用户A的使命看到用户B的数据)。
- 线程数已达最大值:假如线程数已达最大线程数,且队列已满,新使命会被拒绝(根据拒绝战略),也不会创建新线程,因此不会继承。
不但是线程池污染,线程池使用 InheritableThreadLocal 还大概存在获取不到值的情况。比方,在实验异步使命的时间,复用了某个已有的线程A,而且当时创建该线程A的时间,没有继承InheritableThreadLocal,进而导致背面复用该线程的时间,从InheritableThreadLocal获取到的值为null:- public class InheritableThreadLocalWithThreadPoolTest {
- private static final InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
- // 这里线程池core/max数量都只有2
- private static final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
- 2,
- 2,
- 0L,
- TimeUnit.MILLISECONDS,
- new LinkedBlockingQueue<Runnable>(3000),
- new ThreadPoolExecutor.CallerRunsPolicy()
- );
-
- public static void main(String[] args) {
- // 先执行了不涉及InheritableThreadLocal的子任务初始化线程池线程
- testAnotherFunction();
- testAnotherFunction();
- // 后执行了涉及InheritableThreadLocal
- testInheritableThreadLocalWithThreadPool("张三");
- testInheritableThreadLocalWithThreadPool("李四");
- threadPoolExecutor.shutdown();
- }
-
- /** * inheritableThreadLocal+线程池测试 */
- public static void testInheritableThreadLocalWithThreadPool(String param) {
- // 1. 在主线程中设置一个值到inheritableThreadLocal
- inheritableThreadLocal.set(param);
- // 2. 提交异步任务到线程池
- threadPoolExecutor.execute(() -> {
- // 3. 在线程池-子线程里面可以获取到父线程设置的inheritableThreadLocal吗?
- System.out.println("线程名: " + Thread.currentThread().getName() + ", 父线程设置的inheritableThreadLocal值: " + param + ", 子线程获取到inheritableThreadLocal的值: " + inheritableThreadLocal.get());
- });
- // 4. 清除inheritableThreadLocal
- inheritableThreadLocal.remove();
- }
-
- /** * 模拟另一个独立的功能 */
- public static void testAnotherFunction() {
- // 提交异步任务到线程池
- threadPoolExecutor.execute(() -> {
- // 在线程池-子线程里面可以获取到父线程设置的inheritableThreadLocal吗?
- System.out.println("线程名: " + Thread.currentThread().getName() + ", 线程池-子线程摸个鱼");
- });
- }
- }
复制代码 实验效果:- 线程名:pool-1-thread-2,线程池-子线程摸个鱼
- 线程名:pool-1-thread-1,线程池-子线程摸个鱼
- 线程名:pool-1-thread-1,父线程设置的inheritableThreadLocal值:李四,子线程获取到inheritableThreadLocal的值:null
- 线程名:pool-1-thread-2,父线程设置的inheritableThreadLocal值:张三,子线程获取到inheritableThreadLocal的值:null
复制代码 固然了,办理这个题目可以思量使用阿里开源的 TransmittableThreadLocal (TTL),大概在提交异步使命前,先获取线程数据,再传入。比方:- // 1. 在主线程中先获取inheritableThreadLocal的值
- String name = inheritableThreadLocal.get();
-
- // 2. 提交异步任务到线程池
- threadPoolExecutor.execute(() -> {
- // 3. 在线程池-子线程里面直接传入数据
- System.out.println("线程名: " + Thread.currentThread().getName() + ", 父线程设置的inheritableThreadLocal值: " + param + ", 子线程获取到inheritableThreadLocal的值: " + name);
- });
复制代码 与 ThreadLocal 的对比
特性ThreadLocalInheritableThreadLocal数据隔离线程绝对隔离线程绝对隔离子线程继承不支持支持(创建时)底层存储字段Thread.threadLocalsThread.inheritableThreadLocals实用场景线程内全局变量,克制传参父子线程间必要转达上下文数据 |