0. bg
最近在項(xiàng)目中用到threadlocal,threadLocal理解起來很簡單,就是和當(dāng)前線程綁定的一個(gè)map固翰,使用get/set去拿到與線程名的key-value收厨。那么問題來了,1.當(dāng)一個(gè)線程中存在多個(gè)ThreadLocal變量砸讳,線程是如何通過get去得到value的呢琢融?如下:
public class Test {
ThreadLocal<String> arg1=new ThreadLocal<>();
ThreadLocal<String> arg2=new ThreadLocal<>();
ThreadLocal<String> arg3=new ThreadLocal<>();
public static void main(String[] args) {
Test test=new Test();
test.arg1.get(); //如何去映射得到value值
test.arg2.get();
}
}
- 兩個(gè)不同的類中,Test2去獲取到同一個(gè)線程中的Test存儲(chǔ)在ThreadLocal中的值的過程是什么樣的簿寂?如:
public class Test2{
//同一個(gè)線程中漾抬,去獲得test中的threadLocal的值
public static void main(String[] args) {
Test test1=new Test();
test1.arg1.get();
test1.arg2.get();
}
}
3. 如何使用ThreadLocal在同一個(gè)線程中優(yōu)雅的傳值呢?如問題2中常遂,當(dāng)在test1中設(shè)置了threadLocal對(duì)象的值纳令,在test2中我們無法得到實(shí)例test1,那么我們?nèi)绾稳ツ玫絫hreadLocal變量克胳?
so帶著上面的三個(gè)問題探索ThreadLocal.
1. ThreadLocal源碼層面
從注釋出發(fā):
ThreadLocal變量通常是類中與線程關(guān)聯(lián)的私有靜態(tài)域:一個(gè)線程私有ThreadLocal泊碑。例如:類可以通過ThreadLocal給每個(gè)線程生成一個(gè)獨(dú)一無二的標(biāo)識(shí)符。
每個(gè)線程都有一個(gè)本地threadlocal副本變量的隱式的引用毯欣,當(dāng)線程銷毀或沒有其他引用指向該線程的threaLocal的副本馒过,則這些副本將被回收
繼續(xù)往下:ThreadLocals依賴一個(gè)依附于每個(gè)線程的線性hash表,hash表中threadLocal對(duì)象(計(jì)算hash值酗钞,這個(gè)hash值是threadLocal自定義的算法)作為key腹忽。
繼續(xù)往下我們發(fā)現(xiàn)ThreadLocal規(guī)定了未經(jīng)set()而去使用get()方法獲得的值為null,如果我們希望改變這get()得到的null值来累,可以繼承ThreadLocal并重寫
initialValue()
,當(dāng)然還有內(nèi)部類SuppliedThreadLocal
窘奏,它重寫了initialValue()方法
,允許我們提供一個(gè)supplier
去提供初始值嘹锁。如下:
ThreadLocal t= ThreadLocal.withInitial(()->"1");
System.out.println(t.get()); //打印 1
現(xiàn)在到了最重要的方法之一:get()
: 它會(huì)得到當(dāng)前線程的在局部變量在threadLocal中的值,如果沒有值着裹,則調(diào)用initialValue()
獲得返回值领猾。get()方法如下:
public T get() {
Thread t = Thread.currentThread(); //當(dāng)前線程
ThreadLocalMap map = getMap(t);//核心 threadLocalMap
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
對(duì)ThreadLocalMap進(jìn)行研究:顯然,它是一個(gè)map骇扇,每個(gè)線程都有一個(gè)threadLocalMap,那么對(duì)應(yīng)上面兩個(gè)問題摔竿,很難知道key是什么東西。出現(xiàn)問題:什么是弱引用少孝? --弱引用對(duì)象每次GC都會(huì)被回收继低。 繼續(xù)往下走發(fā)現(xiàn),ThreadLocalMap的key就是當(dāng)前的ThreadLocal對(duì)象稍走。那么問題一二解決了袁翁,再看ThreadLocal的get源碼ThreadLocalMap.Entry e = map.getEntry(this);
這里的this即是當(dāng)前threadLocal,故得到vaue婿脸。同時(shí):問題:既然threadLocal只能管理一個(gè)變量粱胜,那么threadLocalMap中為什么要定義一個(gè)Entry的數(shù)組,為什么不定義一個(gè)Entry? ---> 參考上面的代碼狐树,當(dāng)我們在一個(gè)類中焙压,用到了多個(gè)ThreadLocal變量時(shí),這個(gè)時(shí)候褪迟,每個(gè)threadLocal就在后面將key-value放到了Entry數(shù)組里面了冗恨。還應(yīng)該注意到,ThreadLocal是static修飾的味赃,那么是所有的線程公用同一個(gè)ThreadLocal對(duì)象掀抹。
set()
方法同理。至于ThreadLocalMap里面其他的方法心俗,就不累贅分析了傲武,類似集合Map。
2. threadLocal副本城榛?為什么threadLocal要使用變量副本揪利?
threadLocal從字面理解,這個(gè)類為每個(gè)線程都創(chuàng)建了一個(gè)本地變量狠持,實(shí)際上是ThreadLocal為變量在每個(gè)線程中都創(chuàng)建了一個(gè)副本疟位,使得每個(gè)線程都可以訪問自己內(nèi)部的副本變量
通常提到多線程,都會(huì)考慮變量同步的問題喘垂,但是ThreadLocal并不是為了解決多線程共享變量同步的問題甜刻,而是為了讓每個(gè)線程的變量不互相影響绍撞,相當(dāng)于線程之間操縱的都是變量的副本,自然就不用考慮多線程競爭的問題得院,也自然沒有性能損耗傻铣。
3. 問題三解決
通過上述分析,我們知道ThreadLocal是與線程綁定的祥绞,而線程中又共享同一份ThreadLocalMap非洲,key-value都存儲(chǔ)在threadLocalMap中,那么我們只要在同一份ThreadLocal操作就可以了蜕径。即把ThreadLocal做一個(gè)封裝两踏,當(dāng)成類的static對(duì)象即可。如下:
public class ThreadLocalArgs {
private static final ThreadLocal<String> argThreadLocal= new ThreadLocal<>();
public static void set(String argument){
argThreadLocal.set(argument);
}
public static String get(){
return argThreadLocal.get();
}
public static void unset(){
argThreadLocal.remove();//注意當(dāng)前線程用完變量后要remove
}
}
那么在該線程的所有實(shí)例中丧荐,通過調(diào)用ThreadLocalArgs .argThreadLocal即可拿到變量的值缆瓣。