聊一聊线程变量绑定之InheritableThreadLocal
- 2019 年 12 月 19 日
- 筆記
通过上一节我们知道,ThreadLocal 可以用于线程变量绑定和隔离,但是却无法做到服务调用链路很长时,需要做链路追踪时,子线程无法获取到父线程中的共享变量的情况,本节的 InheritableThreadLocal 就是用来解决这个问题的。
示例
@Test public void testInheritableThreadLocal() throws InterruptedException { InheritableThreadLocal threadLocal = new InheritableThreadLocal(); threadLocal.set("simple thread local in main thread!"); Thread thread = new Thread(new Runnable() { @Override public void run() { System.out.println("inner thread:" + threadLocal.get()); threadLocal.set("simple ThreadLocal in thread!"); System.out.println("inner thread:" + threadLocal.get()); } }); thread.start(); thread.join(); System.out.println(threadLocal.get()); }
输出结果为:
inner thread:simple thread local in main thread! inner thread:simple ThreadLocal in thread! simple thread local in main thread!
可以看到,在子线程中拿到了父线程的 threadLocal 变量的值。
源码
我们继续按照分析 ThreadLocal 源码的思路来分析一下 InheritableThreadLocal 变量。
Thread 构造方法
public Thread() { init(null, null, "Thread-" + nextThreadNum(), 0); } private void init(ThreadGroup g, Runnable target, String name, long stackSize) { init(g, target, name, stackSize, null); } private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc) { ..................... if (parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); .............. }
在 Thread 中有一个 inheritableThreadLocals 变量,它的值是在构造方法中赋值的,会将父 Thread 的 inheritableThreadLocals 变量传入到当前子线程的中,并通过 ThreadLocal.createInheritedMap(parent.inheritableThreadLocals)方法创建子线程的 inheritableThreadLocals。
ThreadLocal.createInheritedMap
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) { return new 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) { 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++; } } } }
会将父线程的 ThreadLocalMap 中的 table 中的 Entry 拷到子线程的 ThreadLocalMap 中。
在这里我们尤其需要关注的是这一行:
Object value = key.childValue(e.value)
这在 InheritableThreadLocal 中有对应的实现:
protected T childValue(T parentValue) { return parentValue; }
直接返回的是父线程中的 value。
其他方法
getMap
ThreadLocalMap getMap(Thread t) { return t.inheritableThreadLocals; }
返回的是当前线程的 inheritableThreadLocals 变量。
createMap
void createMap(Thread t, T firstValue) { t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue); }
创建 ThreadLocalMap 的时候也是用的 inheritableThreadLocals 引用。
InheritableThreadLocal 主要用于子线程创建时,需要自动继承父线程的 ThreadLocal 变量,方便必要信息的进一步传递。
问题
我们看下下面这两个示例: 示例一:
@Test public void testMultiThreadWithoutPool(){ InheritableThreadLocal threadLocal = new InheritableThreadLocal(); IntStream.range(0,10).forEach(i -> { threadLocal.set(i); new Thread(() -> { System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get()); }).start(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } }); }
输出结果:
Thread-0:0 Thread-1:1 Thread-2:2 Thread-3:3 Thread-4:4 Thread-5:5 Thread-6:6 Thread-7:7 Thread-8:8 Thread-9:9
示例二:
private ExecutorService service = Executors.newFixedThreadPool(1); @Test public void testInheritableByThreadPool(){ InheritableThreadLocal threadLocal = new InheritableThreadLocal(); IntStream.range(0,10).forEach(i -> { System.out.println(i); threadLocal.set(i); service.submit(() -> { System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get()); }); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } }); }
输出结果:
0 pool-1-thread-1:0 1 pool-1-thread-1:0 2 pool-1-thread-1:0 3 pool-1-thread-1:0 4 pool-1-thread-1:0 5 pool-1-thread-1:0 6 pool-1-thread-1:0 7 pool-1-thread-1:0 8 pool-1-thread-1:0 9 pool-1-thread-1:0
对比两者的结果可以发现,同样的 set 操作,结果大不相同。这是因为示例一是每次 new Thread 的操作都会将父线程的 ThreadLocal 变量传入子线程中,示例二是线程池的操作,线程只会初始化一次,子线程是取不到父线程变量的实时变动的。关于这个问题,下篇文章中将要讲的 TransmittableThreadLocal 可以完美解决。