相關文章:
ThreadLocal 存在的問題
在上一篇文章中簡單介紹了什麼是 ThreadLocal
,以及爲什麼我們可以根據它去操作線程級別的變量。但是 ThreadLocal
嚴格綁定當前線程,不適用於線程切換的情況,比如在分佈式 Tracing 中,你無法控制會不會有服務存在線程切換。先看一個例子:
public class InheritableThreadLocalTest1 {
private static final ThreadLocal<Integer> HOLDER = new ThreadLocal<>();
public static void main(String[] args) {
HOLDER.set(1);
System.out.println(currentName()+":"+HOLDER.get());
new Thread(()-> System.out.println(currentName()+":"+HOLDER.get())).start();
}
private static String currentName(){
return Thread.currentThread().getName();
}
}
輸出結果:
main:1
Thread-0:null
可以發現,在子線程(即 Thread-0
線程)中無法獲取父線程(即 main
線程)的數據,用官方的翻譯來說的話就是 ThreadLocal
不支持繼承。這個也很好理解,雖然都是 Thread
類的實例,但是不同實例之間怎麼可能直接獲取對方成員變量的數據呢。
一種解決思路
一般我們多線程操作都是會比如實現 Runable,封裝一個 Task
類,我們可以提前在 Task
類中傳入父線程的變量,示例代碼如下:
/**
* @author
* @Description 執行任務包裝類
* @Date 創建於 2020-03-29 14:51
*/
public class MyTask implements Runnable {
private Runnable runnable;
private Integer inheritableValue;
@Override
public void run() {
try {
InheritableThreadLocalTest.HOLDER.set(inheritableValue);
runnable.run();
}finally {
InheritableThreadLocalTest.HOLDER.remove();
}
}
public MyTask(Runnable runnable) {
this.runnable = runnable;
this.inheritableValue = InheritableThreadLocalTest.HOLDER.get();
}
}
測試代碼:
public class InheritableThreadLocalTest {
public static final ThreadLocal<Integer> HOLDER = new ThreadLocal<>();
public static void main(String[] args) {
HOLDER.set(1);
System.out.println(currentName() + ":" + HOLDER.get());
Runnable task = ()-> System.out.println(currentName()+":"+HOLDER.get());
new Thread(task,"simple task").start();
new Thread(new MyTask(task),"inheritable task").start();
}
private static String currentName() {
return Thread.currentThread().getName();
}
}
輸出結果:
main:1
simple task:null
inheritable task:1
會發現是可以獲取到父線程的值。JDK 提供了 InheritableThreadLocal
解決 ThreadLocal
繼承的問題。
InheritableThreadLocal
InheritableThreadLocal
繼承了 ThreadLocal
,有這麼幾個方法:
結合上一篇文章中分析的 ThreadLocal#set
方法,優先看下 createMap
方法:
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
可以發現與 ThreadLocal
不同的是,InheritableThreadLocal
關注的是 Thread
的 inheritableThreadLocals
變量:
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
也是 ThreadLocalMap
類型,咋一看並沒有什麼特殊的,參考上文我們自己實現的一種解決思路,我們是在子線程執行方法前將父線程的值設置進去的,看看 JDK 是怎麼做的,先看 Thread#init
方法:
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
...
this.name = name;
//獲取當前線程
Thread parent = currentThread();
...
//inheritThreadLocals 一般情況都是 true
//如果父線程有可繼承的變量,則賦值給當前線程
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
/* Set thread ID */
tid = nextThreadID();
}
後面的 createInheritedMap 方法的細節這裏就不過多贅述了,主要積就是將父線程中所有可繼承的變量賦值給子線程。可以發現,思路都是一致的,就是提前預存父線程中的數據。
歡迎關注公衆號