通過上一節我們知道,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 可以完美解決。