通过上一节我们知道,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 变量。
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。
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。
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
返回的是当前线程的 inheritableThreadLocals 变量。
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 可以完美解决。