最近在工作中结合线程池使用 InheritableThreadLocal
出现了获取线程变量“错误”的问题,看了相关的文档和源码后在此记录。
InheritableThreadLocal
只有在父线程创建子线程时,在子线程中才能获取到父线程中的线程变量;
当配合线程池使用时:“第一次在线程池中开启线程,能在子线程中获取到父线程的线程变量,而当该子线程开启之后,发生线程复用,该子线程仍然保留的是之前开启它的父线程的线程变量,而无法获取当前父线程中新的线程变量”,所以会发生获取线程变量错误的情况。
/** * 测试线程池下InheritableThreadLocal线程变量失效的场景 */ public class TestInheritableThreadLocal { private static final InheritableThreadLocal<String> threadLocal = new InheritableThreadLocal<>(); // 固定大小的线程池,保证线程复用 private static final ExecutorService executorService = Executors.newFixedThreadPool(1); public static void main(String[] args) { threadLocal.set("main线程 变量1"); // 正常取到 main线程 变量1 executorService.execute(() -> System.out.println(threadLocal.get())); threadLocal.set("main线程 变量2"); // 线程复用再取还是 main线程 变量1 executorService.execute(() -> System.out.println(threadLocal.get())); } }
输出结果:
main线程 变量1 main线程 变量1
发现两次输出结果值相同,证明发生线程复用时,子线程获取父线程变量失效
This class extends ThreadLocal to provide inheritance of values from parent thread to child thread: when a child thread is created, the child receives initial values for all inheritable thread-local variables for which the parent has values. Normally the child's values will be identical to the parent's; however, the child's value can be made an arbitrary function of the parent's by overriding the childValue method in this class. Inheritable thread-local variables are used in preference to ordinary thread-local variables when the per-thread-attribute being maintained in the variable (e.g., User ID, Transaction ID) must be automatically transmitted to any child threads that are created.
InheritableThreadLocal
继承了 ThreadLocal
, 以能够让子线程能够从父线程中继承线程变量: 当一个子线程被创建
时,它会接收到父线程中所有可继承的变量。通常情况下,子线程和父线程中的线程变量是完全相同的,但是可以通过重写 childValue
方法来使父子线程中的值不同。
当线程中维护的变量如UserId, TransactionId 等必须自动传递到新创建的任何子线程时,使用InheritableThreadLocal
要优于ThreadLocal
public class InheritableThreadLocal<T> extends ThreadLocal<T> { /** * 当子线程被创建时,通过该方法来初始化子线程中线程变量的值, * 这个方法在父线程中被调用,并且在子线程开启之前。 * * 通过重写这个方法可以改变从父线程中继承过来的值。 * * @param parentValue the parent thread's value * @return the child thread's initial value */ protected T childValue(T parentValue) { return parentValue; } ThreadLocalMap getMap(Thread t) { return t.inheritableThreadLocals; } void createMap(Thread t, T firstValue) { t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue); } }
其中childValue
方法来获取父线程中的线程变量的值,也可通过重写这个方法来将获取到的线程变量的值进行修改。
在getMap
方法和createMap
方法中,可以发现inheritableThreadLocals
变量,它是 ThreadLocalMap
,在Thread类
中
public Thread(ThreadGroup group, String name) { init(group, null, name, 0); }
init
方法的最终实现,其中有如下逻辑:为当前线程创建线程变量以继承父线程中的线程变量/** * @param inheritThreadLocals 为ture,代表是为 包含可继承的线程变量 的线程进行初始化 */ private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) { ... if (inheritThreadLocals && parent.inheritableThreadLocals != null) // 注意这里创建子线程的线程变量 this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); ... }
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals)
创建子线程 InheritedMap
的具体实现
createInheritedMap
方法,最终会调用到 ThreadLocalMap
的私有构造方法,传入的参数parentMap即为父线程中保存的线程变量
private ThreadLocalMap(ThreadLocalMap parentMap) { Entry[] parentTable = parentMap.table; int len = parentTable.length; setThreshold(len); table = new Entry[len]; for (int j = 0; j < len; j++) { Entry e = parentTable[j]; if (e != null) { @SuppressWarnings("unchecked") ThreadLocal<Object> key = (ThreadLocal<Object>) e.get(); if (key != null) { // 注意!!! 这里调用了childValue方法 Object value = key.childValue(e.value); Entry c = new Entry(key, value); int h = key.threadLocalHashCode & (len - 1); while (table[h] != null) h = nextIndex(h, len); table[h] = c; size++; } } } }
这个方法会对父线程中的线程变量做深拷贝,其中调用了childValue
方法来获取/初始化子线程中的值,并保存到子线程中
InheritableThreadLocal
的 createMap
方法,创建 inheritableThreadLocals
并保存线程变量