news 2026/8/27 13:20:04

子线程无法访问父线程中通过ThreadLocal设置的变量

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
子线程无法访问父线程中通过ThreadLocal设置的变量

一、提出现象

在子线程中,是无法访问父线程通过ThreadLocal设置的变量的。

案例代码

/** * @author 沐雨橙风ιε * @version 1.0 */ public class ThreadLocalExample { public static void main(String[] args) throws Exception { ThreadLocal<String> threadLocal = new ThreadLocal<>(); threadLocal.set("hello"); Thread thread = new Thread(new Runnable() { @Override public void run() { System.out.println("run()..."); /* * 子线程中无法访问父线程中设置的ThreadLocal变量 */ System.out.println(threadLocal.get()); } }); thread.start(); thread.join(); System.out.println(threadLocal.get()); } }

运行结果

二、分析原因

究竟是什么原因导致的子线程无法访问父线程中的ThreadLocal变量呢,接下来研究ThreadLocal这个类的源码。

先看一下这个ThreadLocal类上的文档注释,大概了解ThreadLocal是用来干什么的

/** * This class provides thread-local variables. These variables differ from * their normal counterparts in that each thread that accesses one (via its * {@code get} or {@code set} method) has its own, independently initialized * copy of the variable. {@code ThreadLocal} instances are typically private * static fields in classes that wish to associate state with a thread (e.g., * a user ID or Transaction ID). * * <p>For example, the class below generates unique identifiers local to each * thread. * A thread's id is assigned the first time it invokes {@code ThreadId.get()} * and remains unchanged on subsequent calls. * <pre> * import java.util.concurrent.atomic.AtomicInteger; * * public class ThreadId { * // Atomic integer containing the next thread ID to be assigned * private static final AtomicInteger nextId = new AtomicInteger(0); * * // Thread local variable containing each thread's ID * private static final ThreadLocal&lt;Integer&gt; threadId = * new ThreadLocal&lt;Integer&gt;() { * &#64;Override protected Integer initialValue() { * return nextId.getAndIncrement(); * } * }; * * // Returns the current thread's unique ID, assigning it if necessary * public static int get() { * return threadId.get(); * } * } * </pre> * <p>Each thread holds an implicit reference to its copy of a thread-local * variable as long as the thread is alive and the {@code ThreadLocal} * instance is accessible; after a thread goes away, all of its copies of * thread-local instances are subject to garbage collection (unless other * references to these copies exist). * * @author Josh Bloch and Doug Lea * @since 1.2 */

1、关键注释

博主在类的文档注释上提取了几个关于ThreadLocal的重要的说明。

注释1

This class provides thread-local variables.

这个类提供了线程本地变量

注释2

ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread.

ThreadLocal实例提供了可以关联一个线程的静态字段。

注释3

Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal instance is accessible;

after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other references to these copies exist).

只要线程是存活状态,并且ThreadLocal对象可以访问,每个线程都拥有一个线程本地变量的副本的引用;

在线程执行完方法之后,所有线程本地变量都会被gc(垃圾收集器)回收,除非有其他变量引用了它。

2、研究源码

经过上面博主的大致翻译,已经对ThreadLocal有了一定的了解.

接下来深入源码去学习ThreadLocal的工作原理。


在不了解ThreadLocal的情况下,直接从我们直接调用的get()和set()方法入手。

public class ThreadLocal<T> { public T get() { Thread thread = Thread.currentThread(); ThreadLocalMap map = getMap(thread); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { T result = (T)e.value; return result; } } return setInitialValue(); } public void set(T value) { Thread thread = Thread.currentThread(); ThreadLocalMap map = getMap(thread); if (map != null) { map.set(this, value); } else { createMap(t, value); } } }

set()方法

public void set(T value) { // 获取当前线程 Thread thread = Thread.currentThread(); // 根据当前线程获取ThreadLocalMap对象 ThreadLocalMap map = getMap(thread); // map不为null,设置初始值到map中 if (map != null) { map.set(this, value); } else { // map是null,创建一个map createMap(t, value); } }
getMap()

看样子Thead类里的一个threadLocals变量有关。

ThreadLocal.ThreadLocalMap getMap(Thread thread) { // 这个方法就是返回Thread的threadLocals变量的值 return thread.threadLocals; }

ThreadLocalMap是ThreadLocal的一个静态内部类。

public class ThreadLocal<T> { static class ThreadLocalMap { } }
set()

ThreadLocal.ThreadLocalMap的set()方法,有点复杂,类似HashMap的底层源码实现。

private void set(ThreadLocal<?> key, Object value) { ThreadLocal.ThreadLocalMap.Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (ThreadLocal.ThreadLocalMap.Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); if (k == key) { e.value = value; return; } if (k == null) { replaceStaleEntry(key, value, i); return; } } tab[i] = new ThreadLocal.ThreadLocalMap.Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) { rehash(); } }
createMap()
void createMap(Thread thread, T firstValue) { // 初始化线程的threadLocals变量 thread.threadLocals = new ThreadLocal.ThreadLocalMap(this, firstValue); }
set()方法总结

根据set()方法的源代码,set()方法是把值设置到Thead线程类的threadLocals变量中,这个变量应该是一个Map派生类的实例。

get()方法

public T get() { Thread thread = Thread.currentThread(); // 获取当前线程 // 通过getMap()方法获取一个ThreadLocalMap对象 // 因为这个类是以Map结尾的,推测是java.util.Map的派生类 ThreadLocalMap map = getMap(thread); // 获取到的map不为null if (map != null) { // 获取Map的Entry // 说明我们的推测大概率是正确的,因为HashMap中也有个Enty,而且也有value()方法 ThreadLocalMap.Entry e = map.getEntry(this); // 获取的value不为空,则转为指定的类型T(泛型)直接返回 if (e != null) { T result = (T) e.value; return result; } } // 代码运行到这里,说明获取到的map是null // 因为前面的if中已经通过return结束方法 return setInitialValue(); }
getMap()

这个方法的代码在前面set()方法里已经看了~

createMap()

这个方法的代码在前面set()方法里已经看了~

setInitialValue()

这个方法和set()方法的代码几乎一模一样。知识多了一个返回值~

T value = initialValue();

set()方法的代码

return value;

private T setInitialValue() { // 通过initialValue()方法获取一个初始化值 T value = initialValue(); // 获取当前线程 Thread thread = Thread.currentThread(); // 根据当前线程获取ThreadLocalMap对象 ThreadLocalMap map = getMap(thread); // map不为null,设置初始值到map中 if (map != null) { map.set(this, value); } else { // map是null,创建一个map createMap(thread, value); } // 返回初始值 return value; }
get()方法总结

综合上面关于get()方法的源代码,get()方法是从Thead线程类的threadLocals变量中获取数据。

ThreadLocal.ThreadLocalMap

下面是ThreadLocalMap类的一些关键的代码,很显然,这个内部类的结构设计的和HashMap非常相似,通过一个数组table保存值,根据传入的key进行特定的hash运算得到数组的下标,然后获取对应数组下标的值返回。

public class ThreadLocal<T> { static class ThreadLocalMap { static class Entry extends WeakReference<ThreadLocal<?>> { Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } private static final int INITIAL_CAPACITY = 16; private ThreadLocal.ThreadLocalMap.Entry[] table; private int size = 0; private int threshold; private ThreadLocal.ThreadLocalMap.Entry getEntry(ThreadLocal<?> key) { int i = key.threadLocalHashCode & (table.length - 1); ThreadLocal.ThreadLocalMap.Entry e = table[i]; if (e != null && e.get() == key) { return e; } else { return getEntryAfterMiss(key, i, e); } } } }

三、代码总结

文章稍微学习的深入了一点,可能有些童鞋已经懵了,但是回到set()和get()方法的代码可以发现

不管你存储值的代码如何复杂,都是通过当前线程作为key存储在ThreadLocal的静态内部类ThreadLocalMap中的,知道这点其实就够了。

在子线程中,由于通过new Thread()新开了一个线程,那么当代码执行这个新开的子线程的run()方法内部,当前线程已经不是刚开始执行方法的线程了。

比如:在main方法中,执行代码的线程是main线程,也就是主线程,它的线程名就是main。

但是新开一个线程,它的线程名是通过Threa-0开始编号的

通过一个简单的代码了解一下

也就是说,在不同线程中,当前线程已经发生了改变,调用ThreaLocal的get()方法的key变了,自然获取不到我们当初设置的变量值。

四、解决方案

InheritableThreadLocal可以解决这个不可见的问题。

案例代码

/** * @author 沐雨橙风ιε * @version 1.0 */ public class InheritableThreadLocalExample { public static void main(String[] args) throws Exception { InheritableThreadLocal<String> threadLocal = new InheritableThreadLocal<>(); threadLocal.set("hello"); Thread thread = new Thread(new Runnable() { @Override public void run() { System.out.println("run()..."); System.out.println(threadLocal.get()); } }); thread.start(); thread.join(); System.out.println(threadLocal.get()); }

运行结果

探究源码

陷入反思:为什么InheritableThreadLocal中设置的变量就可以在子线程中访问呢?

理清思路:InheritableThreadLocal就比ThreadLocal多了一个单词(Inheritable)。

  • Inherit:意思是继承(动词)
  • Inheritable:able后缀,几乎都是形容词,意思是可继承的。

聊到继承,就要和现实生活里的继承联系到一起了

  • 血脉继承
  • 财产继承
  • ......

这些继承都有一个共同点,那就是都是来源于父母/亲戚(他/她们给的)。


所以,其实继承很简单,就是父线程把InheritableThreadLocal里设置的本地变量给子线程。

核心代码

点开java.lang.Thread类的源代码,找到最常用的构造方法。

Thread()

在构造方法里,只是简单地调用了init()方法。

public Thread(Runnable target) { init(null, target, "Thread-" + nextThreadNum(), 0); }
init()
private void init(ThreadGroup g, Runnable target, String name, long stackSize) { init(g, target, name, stackSize, null, true); } private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) { // 简单的参数校验 if (name == null) { throw new NullPointerException("name cannot be null"); } this.name = name; // 设置线程名称 /** * 获取当前线程 * 作为父线程 */ Thread parent = currentThread(); SecurityManager security = System.getSecurityManager(); if (g == null) { // 线程分组为null // 设置为安全管理器的线程分组 if (security != null) { g = security.getThreadGroup(); } // 设置当前线程的分组为父线程的分组 if (g == null) { g = parent.getThreadGroup(); } } // 看不懂的代码,直接跳过 g.checkAccess(); if (security != null) { if (isCCLOverridden(getClass())) { security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION); } } g.addUnstarted(); // 设置参数 this.group = g; this.daemon = parent.isDaemon(); this.priority = parent.getPriority(); if (security == null || isCCLOverridden(parent.getClass())) this.contextClassLoader = parent.getContextClassLoader(); else this.contextClassLoader = parent.contextClassLoader; this.inheritedAccessControlContext = acc != null ? acc : AccessController.getContext(); this.target = target; setPriority(priority); // 目标代码:如果父线程的inheritableThreadLocals不为null if (inheritThreadLocals && parent.inheritableThreadLocals != null) { // 设置到子线程的inheritableThreadLocals变量中 this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); } this.stackSize = stackSize; // 设置线程ID tid = nextThreadID(); }

目标代码

init()方法的其他代码都不重要,这两行代码已经告诉了你原因。

// 如果父线程的inheritableThreadLocals不为null if (inheritThreadLocals && parent.inheritableThreadLocals != null) { // 设置到子线程的inheritableThreadLocals变量中 this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); }

inheritThreadLocals是一个boolean类型的参数,调用的时候传进来的是true。


好了,文章就分享到这里了,看完不要忘了点赞+收藏哦。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/27 13:16:01

kotlin使用建造者模式自定义对话框

本文实例为大家分享了kotlin自定义对话框的具体代码&#xff0c;供大家参考&#xff0c;具体内容如下 1.CommonDialog 创建我们自己的对话框,继承于系统的Dialog 实现构造方法 class CommonDialog(context: Context?, themeResId: Int) : Dialog(context, themeResId) {}\2. 在…

作者头像 李华
网站建设 2026/8/27 13:13:26

原生PHP网站如何实现从零开始的支付宝支付?

底层原理选择支付宝支付&#xff1a;用户在你的网站上选择商品&#xff0c;并决定使用支付宝进行支付。创建订单&#xff1a;你的网站需要为用户所选的商品创建一个订单&#xff0c;包括商品信息、总价等&#xff0c;并生成一个唯一的订单号。调用支付宝API&#xff1a;你的网站…

作者头像 李华
网站建设 2026/8/27 13:11:42

第265篇 SLAM面试高频题——面试官最爱问的20个SLAM问题

前面将近30篇把SLAM从基础到进阶都过了一遍&#xff0c;从数学原理到具体系统&#xff0c;从传统方法到深度学习。这篇做个收尾&#xff0c;把面试官最爱问的SLAM问题做个汇总。 面试SLAM岗位&#xff08;不管是视觉SLAM还是激光SLAM&#xff09;&#xff0c;面试官的提问思路…

作者头像 李华
网站建设 2026/8/27 13:10:52

UP Board换代Whiskey Lake:AI Core X模块如何重塑工业边缘计算

最早接触 UP Board&#xff0c;还是在第一代 Atom 平台的年代&#xff0c;彼时它在我眼里就是一个“能跑 Windows 的树莓派”。这几年 UP Board 产品线一步步迭代&#xff0c;从 UP Squared 到后来的 UP Xtreme 系列&#xff0c;我注意到它在工业场景里被用得越来越多。最近我又…

作者头像 李华
网站建设 2026/8/27 13:09:58

史上最全Android面试题(带全部答案)2024年最新版

金三银四跳槽季即将来临&#xff0c;今天我们要谈的主题是关于求职。 对于技术人员来说&#xff0c;求职是职业生涯中不可或缺的一部分&#xff0c;而准备一份全面细致的面试题则是减少麻烦的关键。在这个跳槽季&#xff0c;我特意准备了这个系列的文章&#xff0c;一方面是为了…

作者头像 李华
网站建设 2026/8/27 13:09:25

双核MCU架构解析:如何兼得高性能与增强安全

1. 双核 MCU 的架构逻辑&#xff1a;性能与安全为什么能“兼得”这几年做嵌入式项目&#xff0c;明显感觉到双核 MCU 已经不是“旗舰专属”了。随手翻一下主流厂商的产品线&#xff0c;意法半导体的 STM32H7 系列里带双核的型号、恩智浦的 i.MX RT1170、瑞萨的 RA6M5 / RZ 系列…

作者头像 李华