云香丽 發表於 2026-1-19 09:00:00

InheritableThreadLocal,从入门到放弃

<p>InheritableThreadLocal相比ThreadLocal多一个能力:在创建子线程Thread时,子线程Thread会自动继承父线程的InheritableThreadLocal信息到子线程中,进而实现在在子线程获取父线程的InheritableThreadLocal值的目的。</p>
<p>关于ThreadLocal详细内容,可以看这篇文章:史上最全ThreadLocal 详解</p>
<h2 id="和-threadlocal-的区别">和 ThreadLocal 的区别</h2>
<p>举个简单的栗子对比下InheritableThreadLocal和ThreadLocal:</p>
<pre><code class="language-java">public&nbsp;class&nbsp;InheritableThreadLocalTest&nbsp;{&nbsp; &nbsp;&nbsp;
        private&nbsp;static&nbsp;final ThreadLocal&lt;String&gt; threadLocal =&nbsp;new&nbsp;ThreadLocal&lt;&gt;();&nbsp; &nbsp;&nbsp;
        private&nbsp;static&nbsp;final InheritableThreadLocal&lt;String&gt; inheritableThreadLocal =&nbsp;new&nbsp;InheritableThreadLocal&lt;&gt;();&nbsp; &nbsp;&nbsp;

        public&nbsp;static&nbsp;void&nbsp;main(String[]&nbsp;args)&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp;
                testThreadLocal();&nbsp; &nbsp; &nbsp; &nbsp;
                testInheritableThreadLocal();&nbsp; &nbsp;
        }&nbsp; &nbsp;&nbsp;

        /**&nbsp; &nbsp; &nbsp;* threadLocal测试&nbsp; &nbsp; &nbsp;*/&nbsp; &nbsp;&nbsp;
        public&nbsp;static&nbsp;void&nbsp;testThreadLocal()&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp;
                &nbsp;// 在主线程中设置值到threadLocal&nbsp; &nbsp; &nbsp; &nbsp;
                &nbsp;threadLocal.set("我是父线程threadLocal的值");&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;
                &nbsp;// 创建一个新线程并启动&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;
                &nbsp;new&nbsp;Thread(() -&gt; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;
                                &nbsp;// 在子线程里面无法获取到父线程设置的threadLocal,结果为null&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
                                &nbsp;System.out.println("从子线程获取到threadLocal的值: "&nbsp;+ threadLocal.get());&nbsp; &nbsp; &nbsp; &nbsp;    }
                &nbsp;).start();&nbsp; &nbsp;
        &nbsp;}&nbsp; &nbsp;&nbsp;
&nbsp;
        &nbsp;/**&nbsp; &nbsp; &nbsp;* inheritableThreadLocal测试&nbsp; &nbsp; &nbsp;*/&nbsp;
        public&nbsp;static&nbsp;void&nbsp;testInheritableThreadLocal()&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;
                // 在主线程中设置一个值到inheritableThreadLocal&nbsp; &nbsp; &nbsp; &nbsp;
                inheritableThreadLocal.set("我是父线程inheritableThreadLocal的值");&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;
                // 创建一个新线程并启动&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;
                new&nbsp;Thread(() -&gt; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;
                                // 在子线程里面可以自动获取到父线程设置的inheritableThreadLocal&nbsp; &nbsp;
                                System.out.println("从子线程获取到inheritableThreadLocal的值: "&nbsp;+ inheritableThreadLocal.get());&nbsp; &nbsp; &nbsp; &nbsp;
                        }).start();&nbsp; &nbsp;
                }
        }
</code></pre>
<p>执行结果:</p>
<pre><code class="language-text">从子线程获取到threadLocal的值:null
从子线程获取到inheritableThreadLocal的值:我是父线程inheritableThreadLocal的值
</code></pre>
<p>可以看到子线程中可以获取到父线程设置的inheritableThreadLocal值,但不能获取到父线程设置的threadLocal值</p>
<h2 id="实现原理">实现原理</h2>
<p>InheritableThreadLocal 的实现原理相当精妙,它通过在创建子线程的瞬间,“复制”父线程的线程局部变量,从而实现了数据从父线程到子线程的<strong>一次性、创建时</strong>的传递 。</p>
<p>其核心工作原理可以清晰地通过以下序列图展示,它描绘了当父线程创建一个子线程时,数据是如何被传递的:</p>
<div class="mermaid">sequenceDiagram
    participant Parent as 父线程
    participant Thread as Thread构造方法
    participant ITL as InheritableThreadLocal
    participant ThMap as ThreadLocalMap
    participant Child as 子线程

    Parent-&gt;&gt;Thread: 创建 new Thread()
    Note over Parent,Thread: 关键步骤:初始化
    Thread-&gt;&gt;Thread: 调用 init() 方法
    Note over Thread,ITL: 检查父线程的 inheritableThreadLocals
    Thread-&gt;&gt;+ThMap: createInheritedMap(&lt;br/&gt;parent.inheritableThreadLocals)
    ThMap-&gt;&gt;ThMap: 新建一个ThreadLocalMap
    loop 遍历父线程Map中的每个Entry
      ThMap-&gt;&gt;+ITL: 调用 key.childValue(parentValue)
      ITL--&gt;&gt;-ThMap: 返回子线程初始值&lt;br/&gt;(默认返回父值,可重写)
      ThMap-&gt;&gt;ThMap: 将 (key, value) 放入新Map
    end
    ThMap--&gt;&gt;-Thread: 返回新的ThreadLocalMap对象
    Thread-&gt;&gt;Child: 将新Map赋给子线程的&lt;br/&gt;inheritableThreadLocals属性
    Note over Child: 子线程拥有父线程变量的副本
</div><p>下面我们来详细拆解图中的关键环节。</p>
<h3 id="核心实现机制">核心实现机制</h3>
<ol>
<li>**数据结构基础:<code>Thread</code>类内部维护了两个 <code>ThreadLocalMap</code>类型的变量 :
<ul>
<li><code>threadLocals</code>:用于存储普通 <code>ThreadLocal</code>设置的变量副本。</li>
<li><code>inheritableThreadLocals</code>:专门用于存储 <code>InheritableThreadLocal</code>设置的变量副本 。<code>InheritableThreadLocal</code>通过重写 <code>getMap</code>和 <code>createMap</code>方法,使其所有操作都针对 <code>inheritableThreadLocals</code>字段,从而与普通 <code>ThreadLocal</code>分离开 。</li>
</ul>
</li>
<li><strong>继承触发时刻:子线程的创建</strong>。继承行为发生在子线程被创建(即执行 <code>new Thread()</code>)时。在 <code>Thread</code>类的 <code>init</code>方法中,如果判断需要继承(<code>inheritThreadLocals</code>参数为 <code>true</code>)<strong>且</strong>父线程(当前线程)的 <code>inheritableThreadLocals</code>不为 <code>null</code>,则会执行复制逻辑 。</li>
<li><strong>复制过程的核心:<code>createInheritedMap</code></strong>。这是实现复制的核心方法 。它会创建一个新的 <code>ThreadLocalMap</code>,并将父线程 <code>inheritableThreadLocals</code>中的所有条目遍历拷贝到新 Map 中。
<ul>
<li><strong>Key的复制</strong>:Key(即 <code>InheritableThreadLocal</code>对象本身)是直接复制的引用。</li>
<li><strong>Value的生成</strong>:Value 并非直接复制引用,而是通过调用 <code>InheritableThreadLocal</code>的 <code>childValue(T parentValue)</code>方法来生成子线程中的初始值。<strong>默认实现是直接返回父值</strong>(<code>return parentValue;</code>),这意味着对于对象类型,父子线程将共享同一个对象引用 。</li>
</ul>
</li>
</ol>
<h3 id="关键特性与注意事项">关键特性与注意事项</h3>
<ol>
<li><strong>创建时复制,后续独立</strong>:继承只发生一次,即在子线程对象创建的瞬间。此后,父线程和子线程对各自 <code>InheritableThreadLocal</code>变量的修改互不影响 。</li>
<li><strong>在线程池中的局限性</strong>:这是 <code>InheritableThreadLocal</code>最需要警惕的问题。线程池中的线程是复用的,这些线程在首次创建时可能已经从某个父线程继承了值。但当它们被用于执行新的任务时,新的任务提交线程(逻辑上的“父线程”)与工作线程已无直接的创建关系,因此之前继承的值不会更新,这会导致<strong>数据错乱</strong>(如用户A的任务拿到了用户B的信息)或<strong>内存泄漏</strong>​ 。对于线程池场景,应考虑使用阿里开源的 <strong>TransmittableThreadLocal (TTL)</strong>​ 。</li>
<li><strong>浅拷贝与对象共享</strong>:由于 <code>childValue</code>方法默认是浅拷贝,如果存入的是可变对象(如 <code>Map</code>、<code>List</code>),父子线程实际持有的是同一个对象的引用。在一个线程中修改该对象的内部状态,会直接影响另一个线程 。若需隔离,可以重写 <code>childValue</code>方法实现深拷贝 。</li>
<li><strong>内存泄漏风险</strong>:与 <code>ThreadLocal</code>类似,如果线程长时间运行(如线程池中的核心线程),并且未及时调用 <code>remove</code>方法清理,那么该线程的 <code>inheritableThreadLocals</code>会一直持有值的强引用,导致无法被GC回收。良好的实践是在任务执行完毕后主动调用 <code>remove()</code></li>
</ol>
<h3 id="线程池中局限性">线程池中局限性</h3>
<p>一般来说,在真实的业务场景下,没人会直接 new Thread,而都是使用线程池的,因此<code>InheritableThreadLocal</code>在线程池中的使用局限性要额外注意</p>
<p>首先,我们先理解 <code>InheritableThreadLocal</code>的继承前提</p>
<ul>
<li><code>InheritableThreadLocal</code>的继承只发生在 <strong>新线程被创建时</strong>(即 <code>new Thread()</code>并启动时)。在创建过程中,子线程会复制父线程的 <code>InheritableThreadLocal</code>值。</li>
<li>在线程池中,线程是预先创建或按需创建的,并且会被复用。因此,继承只会在线程池<strong>创建新线程</strong>时发生,而不会在复用现有线程时发生。</li>
</ul>
<p>再看线程池创建新线程的条件,对于标准的 <code>ThreadPoolExecutor</code>,新线程的创建遵循以下规则:</p>
<ol>
<li><strong>当前线程数 &lt; 核心线程数</strong>:当提交新任务时,如果当前运行的线程数小于核心线程数,即使有空闲线程,线程池也会创建新线程来处理任务。此时,新线程会继承父线程(提交任务的线程)的 <code>InheritableThreadLocal</code>。</li>
<li><strong>当前线程数 &gt;= 核心线程数 &amp;&amp; 队列已满 &amp;&amp; 线程数 &lt; 最大线程数</strong>:当任务队列已满,且当前线程数小于最大线程数时,线程池会创建新线程来处理任务。同样,新线程会继承父线程的 <code>InheritableThreadLocal</code>。</li>
</ol>
<p>不会继承的场景</p>
<ul>
<li><strong>线程复用</strong>:当线程池中有空闲线程时(例如,当前线程数 &gt;= 核心线程数,但队列未满),任务会被分配给现有线程执行。此时,没有新线程创建,因此不会发生继承。现有线程的 <code>InheritableThreadLocal</code>值保持不变(可能是之前任务设置的值),这可能导致数据错乱(如用户A的任务看到用户B的数据)。</li>
<li><strong>线程数已达最大值</strong>:如果线程数已达最大线程数,且队列已满,新任务会被拒绝(根据拒绝策略),也不会创建新线程,因此不会继承。</li>
</ul>
<p>不只是线程池污染,线程池使用 <code>InheritableThreadLocal</code> 还可能存在获取不到值的情况。例如,在执行异步任务的时候,复用了某个已有的线程A,并且当时创建该线程A的时候,没有继承InheritableThreadLocal,进而导致后面复用该线程的时候,从InheritableThreadLocal获取到的值为null:</p>
<pre><code class="language-java">public&nbsp;class&nbsp;InheritableThreadLocalWithThreadPoolTest&nbsp;{&nbsp; &nbsp;&nbsp;
        private&nbsp;static&nbsp;final InheritableThreadLocal&lt;String&gt; inheritableThreadLocal =&nbsp;new&nbsp;InheritableThreadLocal&lt;&gt;();&nbsp; &nbsp;&nbsp;
        // 这里线程池core/max数量都只有2&nbsp; &nbsp;&nbsp;
        private&nbsp;static&nbsp;final ThreadPoolExecutor threadPoolExecutor =&nbsp;new&nbsp;ThreadPoolExecutor(&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;
                2,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;
                2,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;
                0L,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
                TimeUnit.MILLISECONDS,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;
                new&nbsp;LinkedBlockingQueue&lt;Runnable&gt;(3000),&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;
                new&nbsp;ThreadPoolExecutor.CallerRunsPolicy()&nbsp; &nbsp;
        );&nbsp; &nbsp;&nbsp;
       
        public&nbsp;static&nbsp;void&nbsp;main(String[]&nbsp;args)&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;
        // 先执行了不涉及InheritableThreadLocal的子任务初始化线程池线程&nbsp;
       &nbsp; &nbsp; &nbsp; testAnotherFunction();&nbsp; &nbsp; &nbsp; &nbsp;
       &nbsp; &nbsp; &nbsp; testAnotherFunction();&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;
       &nbsp; &nbsp; &nbsp; // 后执行了涉及InheritableThreadLocal
       &nbsp; &nbsp; &nbsp; testInheritableThreadLocalWithThreadPool("张三");&nbsp; &nbsp; &nbsp; &nbsp;
       &nbsp; &nbsp; &nbsp; testInheritableThreadLocalWithThreadPool("李四");&nbsp; &nbsp; &nbsp; &nbsp;
       &nbsp; &nbsp; &nbsp; threadPoolExecutor.shutdown();&nbsp; &nbsp;
       }&nbsp; &nbsp;&nbsp;
       
       /**&nbsp; &nbsp; &nbsp;* inheritableThreadLocal+线程池测试&nbsp; &nbsp; &nbsp;*/&nbsp; &nbsp;&nbsp;
        &nbsp; &nbsp; public&nbsp;static&nbsp;void&nbsp;testInheritableThreadLocalWithThreadPool(String param)&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;
                &nbsp; &nbsp; // 1. 在主线程中设置一个值到inheritableThreadLocal&nbsp; &nbsp; &nbsp; &nbsp;
       &nbsp; &nbsp; &nbsp; &nbsp; inheritableThreadLocal.set(param);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;
       &nbsp; &nbsp; &nbsp; &nbsp;// 2. 提交异步任务到线程池&nbsp; &nbsp; &nbsp; &nbsp;
       &nbsp; &nbsp; &nbsp; &nbsp;threadPoolExecutor.execute(() -&gt; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;
       &nbsp; &nbsp; &nbsp; &nbsp;// 3. 在线程池-子线程里面可以获取到父线程设置的inheritableThreadLocal吗?&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
               &nbsp; &nbsp; &nbsp; &nbsp;System.out.println("线程名: "&nbsp;+ Thread.currentThread().getName() +&nbsp;", 父线程设置的inheritableThreadLocal值: "&nbsp;+ param +&nbsp;", 子线程获取到inheritableThreadLocal的值: "&nbsp;+ inheritableThreadLocal.get());&nbsp; &nbsp; &nbsp; &nbsp;
       &nbsp; &nbsp; &nbsp; &nbsp;});&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;
       &nbsp; &nbsp; &nbsp; &nbsp;// 4. 清除inheritableThreadLocal&nbsp; &nbsp; &nbsp; &nbsp;
       &nbsp; &nbsp; &nbsp; &nbsp;inheritableThreadLocal.remove();&nbsp; &nbsp;
       &nbsp; }&nbsp; &nbsp;&nbsp;
       &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
       &nbsp; /**&nbsp; &nbsp; &nbsp;* 模拟另一个独立的功能&nbsp; &nbsp; &nbsp;*/&nbsp; &nbsp;
       &nbsp; public&nbsp;static&nbsp;void&nbsp;testAnotherFunction()&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;
               &nbsp; // 提交异步任务到线程池&nbsp; &nbsp; &nbsp; &nbsp;
       &nbsp; &nbsp; &nbsp; threadPoolExecutor.execute(() -&gt; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;
       &nbsp; &nbsp; &nbsp; // 在线程池-子线程里面可以获取到父线程设置的inheritableThreadLocal吗?&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
               &nbsp; &nbsp; &nbsp; System.out.println("线程名: "&nbsp;+ Thread.currentThread().getName() +&nbsp;", 线程池-子线程摸个鱼");&nbsp; &nbsp; &nbsp; &nbsp;
       &nbsp; &nbsp; &nbsp; });&nbsp; &nbsp;
       &nbsp; }
}
</code></pre>
<p>执行结果:</p>
<pre><code class="language-text">线程名:pool-1-thread-2,线程池-子线程摸个鱼
线程名:pool-1-thread-1,线程池-子线程摸个鱼
线程名:pool-1-thread-1,父线程设置的inheritableThreadLocal值:李四,子线程获取到inheritableThreadLocal的值:null
线程名:pool-1-thread-2,父线程设置的inheritableThreadLocal值:张三,子线程获取到inheritableThreadLocal的值:null
</code></pre>
<p>当然了,解决这个问题可以考虑使用阿里开源的 <strong>TransmittableThreadLocal (TTL)</strong>,​或者在提交异步任务前,先获取线程数据,再传入。例如:</p>
<pre><code class="language-java">// 1. 在主线程中先获取inheritableThreadLocal的值
String name = inheritableThreadLocal.get();&nbsp; &nbsp;
&nbsp; &nbsp;&nbsp;
// 2. 提交异步任务到线程池&nbsp; &nbsp; &nbsp; &nbsp;
threadPoolExecutor.execute(() -&gt; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;
// 3. 在线程池-子线程里面直接传入数据&nbsp;
System.out.println("线程名: "&nbsp;+ Thread.currentThread().getName() +&nbsp;", 父线程设置的inheritableThreadLocal值: "&nbsp;+ param +&nbsp;", 子线程获取到inheritableThreadLocal的值: "&nbsp;+ name);&nbsp; &nbsp; &nbsp; &nbsp;
       &nbsp; &nbsp; &nbsp; &nbsp;});&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;
</code></pre>
<h2 id="与-threadlocal-的对比">与 ThreadLocal 的对比</h2>
<table>
<thead>
<tr>
<th>特性</th>
<th>ThreadLocal</th>
<th>InheritableThreadLocal</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>数据隔离</strong>​</td>
<td>线程绝对隔离</td>
<td>线程绝对隔离</td>
</tr>
<tr>
<td><strong>子线程继承</strong>​</td>
<td><strong>不支持</strong>​</td>
<td><strong>支持</strong>(创建时)</td>
</tr>
<tr>
<td><strong>底层存储字段</strong>​</td>
<td><code>Thread.threadLocals</code></td>
<td><code>Thread.inheritableThreadLocals</code></td>
</tr>
<tr>
<td><strong>适用场景</strong>​</td>
<td>线程内全局变量,避免传参</td>
<td><strong>父子线程间</strong>需要传递上下文数据</td>
</tr>
</tbody>
</table>


</div>
<div id="MySignature" role="contentinfo">
    <p>本文来自在线网站:seven的菜鸟成长之路,作者:seven,转载请注明原文链接:www.seven97.top</p><br><br>
来源:https://www.cnblogs.com/sevencoding/p/19484504
頁: [1]
查看完整版本: InheritableThreadLocal,从入门到放弃