ThreadLocal使用與原理剖析

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();
    }
}
  1. 兩個(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源碼注釋

ThreadLocal變量通常是類中與線程關(guān)聯(lián)的私有靜態(tài)域:一個(gè)線程私有ThreadLocal泊碑。例如:類可以通過ThreadLocal給每個(gè)線程生成一個(gè)獨(dú)一無二的標(biāo)識(shí)符。
ThreadLocal源碼注釋

每個(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即可拿到變量的值缆瓣。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末喧枷,一起剝皮案震驚了整個(gè)濱河市虹统,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌隧甚,老刑警劉巖车荔,帶你破解...
    沈念sama閱讀 211,743評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異戚扳,居然都是意外死亡忧便,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門帽借,熙熙樓的掌柜王于貴愁眉苦臉地迎上來珠增,“玉大人,你說我怎么就攤上這事砍艾〉俳蹋” “怎么了?”我有些...
    開封第一講書人閱讀 157,285評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵脆荷,是天一觀的道長凝垛。 經(jīng)常有香客問我,道長蜓谋,這世上最難降的妖魔是什么梦皮? 我笑而不...
    開封第一講書人閱讀 56,485評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮桃焕,結(jié)果婚禮上剑肯,老公的妹妹穿的比我還像新娘。我一直安慰自己观堂,他們只是感情好让网,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,581評(píng)論 6 386
  • 文/花漫 我一把揭開白布岖妄。 她就那樣靜靜地躺著,像睡著了一般寂祥。 火紅的嫁衣襯著肌膚如雪荐虐。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,821評(píng)論 1 290
  • 那天丸凭,我揣著相機(jī)與錄音福扬,去河邊找鬼。 笑死惜犀,一個(gè)胖子當(dāng)著我的面吹牛铛碑,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播虽界,決...
    沈念sama閱讀 38,960評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼汽烦,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了莉御?” 一聲冷哼從身側(cè)響起撇吞,我...
    開封第一講書人閱讀 37,719評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎礁叔,沒想到半個(gè)月后牍颈,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,186評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡琅关,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,516評(píng)論 2 327
  • 正文 我和宋清朗相戀三年煮岁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片涣易。...
    茶點(diǎn)故事閱讀 38,650評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡画机,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出新症,到底是詐尸還是另有隱情步氏,我是刑警寧澤,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布账劲,位于F島的核電站戳护,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏瀑焦。R本人自食惡果不足惜腌且,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,936評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望榛瓮。 院中可真熱鬧铺董,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至重付,卻和暖如春顷级,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背确垫。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評(píng)論 1 266
  • 我被黑心中介騙來泰國打工弓颈, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人删掀。 一個(gè)月前我還...
    沈念sama閱讀 46,370評(píng)論 2 360
  • 正文 我出身青樓翔冀,卻偏偏與公主長得像,于是被迫代替她去往敵國和親披泪。 傳聞我的和親對(duì)象是個(gè)殘疾皇子纤子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,527評(píng)論 2 349