Java中的ThreadLocal詳解

寫(xiě)在前面

早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal為解決多線程程序的并發(fā)問(wèn)題提供了一種新的思路硕蛹。使用這個(gè)工具類可以很簡(jiǎn)潔地編寫(xiě)出優(yōu)美的多線程程序掌实。當(dāng)使用ThreadLocal維護(hù)變量時(shí)额衙,ThreadLocal為每個(gè)使用該變量的線程提供獨(dú)立的變量副本虏杰,所以每一個(gè)線程都可以獨(dú)立地改變自己的副本,而不會(huì)影響其它線程所對(duì)應(yīng)的副本腋腮。從線程的角度看雀彼,目標(biāo)變量就象是線程的本地變量,這也是類名中“Local”所要表達(dá)的意思即寡。本篇文章將帶你詳細(xì)的剖析一下ThreadLocal徊哑,以及在開(kāi)發(fā)中的使用。

代碼分析

首先ThreadLocal這個(gè)類是位于java.lang這個(gè)包中嘿悬,具體源碼可以自行查看实柠。那么ThreadLocal到底是干嘛用的呢?其實(shí)它主要就用來(lái)在當(dāng)前線程中存取變量用的善涨,存取變量就存取變量怎么多了一句當(dāng)前線程中?不要急接著往下看。說(shuō)到存取變量我們平時(shí)可能用到的例如:List草则,Map钢拧,數(shù)組等,那么ThreadLocal又是通過(guò)什么樣方式進(jìn)行變量的存取呢炕横?答案就是Map(鍵值對(duì)),沒(méi)錯(cuò)就是Map源内,而這個(gè)Map不是其他的Map而是ThreadLocalMap,ThreadLocalMap是ThreadLocal的靜態(tài)內(nèi)部類份殿,在每一個(gè)線程Thread中都有一個(gè)ThreadLocalMap的成員變量,不信的話看一下Thread類的源碼:

public class Thread implements Runnable {
  .......這里省略掉之前的代碼

       ThreadLocal.ThreadLocalMap threadLocals = null;

 .......這里省略掉之后的代碼
}

這里你會(huì)發(fā)現(xiàn)在Thread類中確實(shí)是有一個(gè)ThreadLocalMap的成員變量threadLocals膜钓,可以很明確的告訴你ThreadLocal就是在操作這個(gè)Thread中的threadLocals嗽交,來(lái)進(jìn)行變量的存取,置于為什么ThreadLocal能操作Thread中的threadLocals颂斜,以及如何操作的后面通過(guò)源碼的分析更進(jìn)一步的了解夫壁。截止到目前你需要知道以下幾點(diǎn):

  • 1、 ThreadLocal是在用于存取變量的
  • 2沃疮、 ThreadLocal是在當(dāng)前線程中存取變量的
  • 3盒让、 ThreadLocal是采用Map,也就是鍵值對(duì)的形式進(jìn)行變量存取的

我們先看看如何使用的司蔬,然后在分析分析源碼邑茄,例如下面就是使用ThreadLocal的一個(gè)例子:

public class MyClass {

    //定義了兩個(gè)ThreadLocal分別為:local1,local2
    private static ThreadLocal<String> local1 = new ThreadLocal<>();

    private static ThreadLocal<Boolean> local2 = new ThreadLocal<Boolean>() {
        @Override
        protected Boolean initialValue() {
            return false;
        }
    };

    public static void main(String[] args) {
        System.out.println("local1:" + local1.get());//運(yùn)行結(jié)果:local1:null
        System.out.println("local2:" + local2.get());//運(yùn)行結(jié)果:local2:false
        local1.set("我是local1");
        local2.set(true);
        System.out.println("local1:" + local1.get());//運(yùn)行結(jié)果:local1:我是local1
        System.out.println("local2:" + local2.get());//運(yùn)行結(jié)果:local2:true
    }
}

通過(guò)以上代碼你會(huì)發(fā)現(xiàn)我在未進(jìn)行任何存儲(chǔ)操作即未調(diào)用set(T t)方法之前:local1返回的是null俊啼,local2返回的卻是false;這就和local2重寫(xiě)了initialValue方法有關(guān)肺缕,具體請(qǐng)看ThreadLocal源碼分析(這里剔除掉了和我們使用不相干的代碼):

package java.lang;//所在包

public class ThreadLocal<T> {
     ...  //省略掉之前的代碼
 
    //這個(gè)方法看到?jīng)]是protected的,也就是說(shuō)希望我們重寫(xiě)的授帕,默認(rèn)返回null
   //初始化值搓谆,在未存儲(chǔ)任何值的前提下,去取值返回的結(jié)果豪墅,默認(rèn)返回null
    protected T initialValue() {
        return null;
    }

   //從ThreadLocalMap中獲取存儲(chǔ)的變量
    public T get() {
         //先獲取當(dāng)前的線程對(duì)象
        Thread t = Thread.currentThread();
        //再獲取線程對(duì)象中的ThreadLocalMap成員變量:threadLocals
        ThreadLocalMap map = getMap(t);
        if (map != null) {
         //如果不為空泉手,則以自己注意是自己為鍵,來(lái)獲取對(duì)應(yīng)的value
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
              //如果value不為空偶器,則直接返回
                return (T)e.value;
        }
        return setInitialValue();
    }

 //往ThreadLocalMap中加入一個(gè)變量
    public void set(T value) {
       //先獲取當(dāng)前的線程對(duì)象
        Thread t = Thread.currentThread();
      //再獲取線程對(duì)象中的ThreadLocalMap成員變量:threadLocals
        ThreadLocalMap map = getMap(t);
      //判斷返回的ThreadLocalMap是否為空
        if (map != null)
           //如果不為空斩萌,則以自己注意是自己為鍵,value參數(shù)為值保寸到線程的ThreadLocalMap中
            map.set(this, value);
        else
          //如果為空屏轰,則創(chuàng)建一個(gè)ThreadLocalMap颊郎,并以自己注意是自己為鍵,value參數(shù)為值霎苗,保存到該Map中同時(shí)將該Map賦值給當(dāng)前線程中的ThreadLocalMap成員變量:threadLocals
            createMap(t, value);
    }


 //移除掉ThreadLocalMap存儲(chǔ)的變量
 //該方法在jdk1.5加入
     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }
   
     static class ThreadLocalMap {
         //省略ThreadLocalMap的具體實(shí)現(xiàn)
           ......
           ......
     }
}

其實(shí)我們?cè)谑褂肨hreadLocal進(jìn)行變量存取操作的時(shí)候用到的也就是以上的幾個(gè)方法:initialValue()姆吭,get(),set()唁盏,remove()内狸。類比Map的操作就好,set就是存厘擂,get就是取昆淡,remove就是刪。

這里需要注意的是ThreadLocal進(jìn)行變量存儲(chǔ)的時(shí)候都是以自身(即this)為鍵來(lái)進(jìn)行存儲(chǔ)的刽严,所以存儲(chǔ)的是set方法而不是put或add昂灵,因?yàn)橐粋€(gè)ThreadLocal對(duì)象就是一個(gè)鍵,一個(gè)鍵在Map中只能對(duì)應(yīng)一個(gè)值;你要想往當(dāng)前線程的Map中存幾個(gè)不同的值眨补,那就需要幾個(gè)不同的鍵管削,即要?jiǎng)?chuàng)建幾個(gè)ThreadLocal對(duì)像,當(dāng)然不同線程中也可以同時(shí)使用同一個(gè)ThreadLocal作為鍵來(lái)存取不同的值撑螺,他們之間對(duì)值的操作互不影響含思。打個(gè)比方:

我用同一個(gè)身份證號(hào)(同一個(gè)ThreadLocal對(duì)象),在A商場(chǎng)(線程A)注冊(cè)了VIP,在B商場(chǎng)(線程B)也注冊(cè)了VIP实蓬,有一天我將A商場(chǎng)的VIP改變?yōu)榱薙VIP茸俭,那我在B商場(chǎng)依舊是VIP而不會(huì)因?yàn)槲腋淖兞薃商場(chǎng)的身份會(huì)影響到我B商場(chǎng)。

總結(jié)

ThreadLocal其實(shí)還是蠻有用的在Android的UI線程中Handler的Looper就是通過(guò)ThreadLocal存儲(chǔ)在當(dāng)前線程中的安皱,所以你只要在UI線程中創(chuàng)建的Handler用的都是之前已經(jīng)在該線程存儲(chǔ)好的Looper调鬓。一些開(kāi)源庫(kù)其實(shí)很多都用到了ThreadLocal,例如何洪輝的EventBus酌伊,其他的這里不做過(guò)多介紹腾窝,有興趣的可以去看看一些好的開(kāi)源庫(kù)的代碼,收獲會(huì)很大居砖。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末虹脯,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子奏候,更是在濱河造成了極大的恐慌循集,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,331評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蔗草,死亡現(xiàn)場(chǎng)離奇詭異咒彤,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)咒精,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,372評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門(mén)镶柱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人模叙,你說(shuō)我怎么就攤上這事歇拆。” “怎么了范咨?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,755評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵故觅,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我湖蜕,道長(zhǎng)逻卖,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,528評(píng)論 1 296
  • 正文 為了忘掉前任昭抒,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘灭返。我一直安慰自己盗迟,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,526評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布熙含。 她就那樣靜靜地躺著罚缕,像睡著了一般。 火紅的嫁衣襯著肌膚如雪怎静。 梳的紋絲不亂的頭發(fā)上邮弹,一...
    開(kāi)封第一講書(shū)人閱讀 52,166評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音蚓聘,去河邊找鬼腌乡。 笑死,一個(gè)胖子當(dāng)著我的面吹牛夜牡,可吹牛的內(nèi)容都是我干的与纽。 我是一名探鬼主播,決...
    沈念sama閱讀 40,768評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼塘装,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼急迂!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起蹦肴,我...
    開(kāi)封第一講書(shū)人閱讀 39,664評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤僚碎,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后阴幌,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體勺阐,經(jīng)...
    沈念sama閱讀 46,205評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,290評(píng)論 3 340
  • 正文 我和宋清朗相戀三年裂七,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了皆看。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,435評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡背零,死狀恐怖腰吟,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情徙瓶,我是刑警寧澤毛雇,帶...
    沈念sama閱讀 36,126評(píng)論 5 349
  • 正文 年R本政府宣布,位于F島的核電站侦镇,受9級(jí)特大地震影響灵疮,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜壳繁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,804評(píng)論 3 333
  • 文/蒙蒙 一震捣、第九天 我趴在偏房一處隱蔽的房頂上張望荔棉。 院中可真熱鬧,春花似錦蒿赢、人聲如沸润樱。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,276評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)壹若。三九已至,卻和暖如春皂冰,著一層夾襖步出監(jiān)牢的瞬間店展,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,393評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工秃流, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留赂蕴,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,818評(píng)論 3 376
  • 正文 我出身青樓剔应,卻偏偏與公主長(zhǎng)得像睡腿,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子峻贮,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,442評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容