尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

和 ThreadLocal 的区别

和 ThreadLocal 的区别
📅 发布时间:2026/7/1 10:41:01

举个简单的栗子对比下InheritableThreadLocal和ThreadLocal:

java

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(); } }

执行结果:

text

从子线程获取到threadLocal的值:null 从子线程获取到inheritableThreadLocal的值:我是父线程inheritableThreadLocal的值

可以看到子线程中可以获取到父线程设置的inheritableThreadLocal值,但不能获取到父线程设置的threadLocal值

实现原理

InheritableThreadLocal 的实现原理相当精妙,它通过在创建子线程的瞬间,“复制”父线程的线程局部变量,从而实现了数据从父线程到子线程的一次性、创建时的传递 。

其核心工作原理可以清晰地通过以下序列图展示,它描绘了当父线程创建一个子线程时,数据是如何被传递的:

子线程ThreadLocalMapInheritableThreadLocalThread构造方法父线程子线程ThreadLocalMapInheritableThreadLocalThread构造方法父线程关键步骤:初始化检查父线程的 inheritableThreadLocalsloop[遍历父线程Map中的每个Entry]子线程拥有父线程变量的副本创建 new Thread()调用 init() 方法createInheritedMap(parent.inheritableThreadLocals)新建一个ThreadLocalMap调用 key.childValue(parentValue)返回子线程初始值(默认返回父值,可重写)将 (key, value) 放入新Map返回新的ThreadLocalMap对象将新Map赋给子线程的inheritableThreadLocals属性

下面我们来详细拆解图中的关键环节。

核心实现机制

  1. **数据结构基础:Thread类内部维护了两个ThreadLocalMap类型的变量 :
    • threadLocals:用于存储普通ThreadLocal设置的变量副本。
    • inheritableThreadLocals:专门用于存储InheritableThreadLocal设置的变量副本 。InheritableThreadLocal通过重写getMap和createMap方法,使其所有操作都针对inheritableThreadLocals字段,从而与普通ThreadLocal分离开 。
  2. 继承触发时刻:子线程的创建。继承行为发生在子线程被创建(即执行new Thread())时。在Thread类的init方法中,如果判断需要继承(inheritThreadLocals参数为true)且父线程(当前线程)的inheritableThreadLocals不为null,则会执行复制逻辑 。
  3. 复制过程的核心:createInheritedMap。这是实现复制的核心方法 。它会创建一个新的ThreadLocalMap,并将父线程inheritableThreadLocals中的所有条目遍历拷贝到新 Map 中。
    • Key的复制:Key(即InheritableThreadLocal对象本身)是直接复制的引用。
    • Value的生成:Value 并非直接复制引用,而是通过调用InheritableThreadLocal的childValue(T parentValue)方法来生成子线程中的初始值。默认实现是直接返回父值(return parentValue;),这意味着对于对象类型,父子线程将共享同一个对象引用 。

关键特性与注意事项

  1. 创建时复制,后续独立:继承只发生一次,即在子线程对象创建的瞬间。此后,父线程和子线程对各自InheritableThreadLocal变量的修改互不影响 。
  2. 在线程池中的局限性:这是InheritableThreadLocal最需要警惕的问题。线程池中的线程是复用的,这些线程在首次创建时可能已经从某个父线程继承了值。但当它们被用于执行新的任务时,新的任务提交线程(逻辑上的“父线程”)与工作线程已无直接的创建关系,因此之前继承的值不会更新,这会导致数据错乱(如用户A的任务拿到了用户B的信息)或内存泄漏​ 。对于线程池场景,应考虑使用阿里开源的TransmittableThreadLocal (TTL)​ 。
  3. 浅拷贝与对象共享:由于childValue方法默认是浅拷贝,如果存入的是可变对象(如Map、List),父子线程实际持有的是同一个对象的引用。在一个线程中修改该对象的内部状态,会直接影响另一个线程 。若需隔离,可以重写childValue方法实现深拷贝 。
  4. 内存泄漏风险:与ThreadLocal类似,如果线程长时间运行(如线程池中的核心线程),并且未及时调用remove方法清理,那么该线程的inheritableThreadLocals会一直持有值的强引用,导致无法被GC回收。良好的实践是在任务执行完毕后主动调用remove()

线程池中局限性

一般来说,在真实的业务场景下,没人会直接 new Thread,而都是使用线程池的,因此InheritableThreadLocal在线程池中的使用局限性要额外注意

首先,我们先理解InheritableThreadLocal的继承前提

  • InheritableThreadLocal的继承只发生在新线程被创建时(即new Thread()并启动时)。在创建过程中,子线程会复制父线程的InheritableThreadLocal值。
  • 在线程池中,线程是预先创建或按需创建的,并且会被复用。因此,继承只会在线程池创建新线程时发生,而不会在复用现有线程时发生。

再看线程池创建新线程的条件,对于标准的ThreadPoolExecutor,新线程的创建遵循以下规则:

  1. 当前线程数 < 核心线程数:当提交新任务时,如果当前运行的线程数小于核心线程数,即使有空闲线程,线程池也会创建新线程来处理任务。此时,新线程会继承父线程(提交任务的线程)的InheritableThreadLocal。
  2. 当前线程数 >= 核心线程数 && 队列已满 && 线程数 < 最大线程数:当任务队列已满,且当前线程数小于最大线程数时,线程池会创建新线程来处理任务。同样,新线程会继承父线程的InheritableThreadLocal。

不会继承的场景

  • 线程复用:当线程池中有空闲线程时(例如,当前线程数 >= 核心线程数,但队列未满),任务会被分配给现有线程执行。此时,没有新线程创建,因此不会发生继承。现有线程的InheritableThreadLocal值保持不变(可能是之前任务设置的值),这可能导致数据错乱(如用户A的任务看到用户B的数据)。
  • 线程数已达最大值:如果线程数已达最大线程数,且队列已满,新任务会被拒绝(根据拒绝策略),也不会创建新线程,因此不会继承。

不只是线程池污染,线程池使用InheritableThreadLocal还可能存在获取不到值的情况。例如,在执行异步任务的时候,复用了某个已有的线程A,并且当时创建该线程A的时候,没有继承InheritableThreadLocal,进而导致后面复用该线程的时候,从InheritableThreadLocal获取到的值为null:

java

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() + ", 线程池-子线程摸个鱼"); }); } }

执行结果:

text

线程名:pool-1-thread-2,线程池-子线程摸个鱼 线程名:pool-1-thread-1,线程池-子线程摸个鱼 线程名:pool-1-thread-1,父线程设置的inheritableThreadLocal值:李四,子线程获取到inheritableThreadLocal的值:null 线程名:pool-1-thread-2,父线程设置的inheritableThreadLocal值:张三,子线程获取到inheritableThreadLocal的值:null

当然了,解决这个问题可以考虑使用阿里开源的TransmittableThreadLocal (TTL),​或者在提交异步任务前,先获取线程数据,再传入。例如:

java

// 1. 在主线程中先获取inheritableThreadLocal的值 String name = inheritableThreadLocal.get(); // 2. 提交异步任务到线程池 threadPoolExecutor.execute(() -> { // 3. 在线程池-子线程里面直接传入数据 System.out.println("线程名: " + Thread.currentThread().getName() + ", 父线程设置的inheritableThreadLocal值: " + param + ", 子线程获取到inheritableThreadLocal的值: " + name); });

与 ThreadLocal 的对比

相关新闻

  • 06.30.每日总结
  • 回收化学原料单位
  • HunterPie游戏内存监控系统架构解析与插件开发实践

最新新闻

  • 网盘直链下载神器LinkSwift:一键获取九大网盘真实下载地址的终极指南
  • LP5812与PIC18LF25K50的智能灯光控制方案详解
  • 基于A89307与PIC18F4525的高性能FOC电机控制方案
  • 基于STM32和A89307的15A无刷电机FOC控制方案
  • ICM-42688-P与PIC24FJ128GA310在运动控制与振动监测中的应用
  • 4-20mA电流环接收器设计与STM32F427ZI应用

日新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

周新闻

  • Windows字体自定义终极方案:No!! MeiryoUI完全指南
  • Deepin Boot Maker:告别命令行,3分钟制作Linux启动盘的智能解决方案
  • Plain Craft Launcher 2:重新定义你的Minecraft游戏体验

月新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号