ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

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

子线程无法访问父线程中通过ThreadLocal设置的变量 一、提出现象在子线程中是无法访问父线程通过ThreadLocal设置的变量的。案例代码/** * author 沐雨橙风ιε * version 1.0 */ public class ThreadLocalExample { public static void main(String[] args) throws Exception { ThreadLocalString 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). * * pFor example, the class below generates unique identifiers local to each * thread. * A threads 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 threads ID * private static final ThreadLocallt;Integergt; threadId * new ThreadLocallt;Integergt;() { * #64;Override protected Integer initialValue() { * return nextId.getAndIncrement(); * } * }; * * // Returns the current threads unique ID, assigning it if necessary * public static int get() { * return threadId.get(); * } * } * /pre * pEach 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的重要的说明。注释1This class provides thread-local variables.这个类提供了线程本地变量注释2ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread.ThreadLocal实例提供了可以关联一个线程的静态字段。注释3Each 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 ThreadLocalT { 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 ThreadLocalT { 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 ThreadLocalT { static class ThreadLocalMap { static class Entry extends WeakReferenceThreadLocal? { 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 { InheritableThreadLocalString 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意思是继承动词Inheritableable后缀几乎都是形容词意思是可继承的。聊到继承就要和现实生活里的继承联系到一起了血脉继承财产继承......这些继承都有一个共同点那就是都是来源于父母/亲戚他/她们给的。所以其实继承很简单就是父线程把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。好了文章就分享到这里了看完不要忘了点赞收藏哦。
返回列表