ThreadLocal和InheritableThreadLocal

ThreadLocal

概述

ThreadLocal一般稱為線程本地變量未桥,它是一種特殊的線程綁定機制颗圣,將變量與線程綁定在一起句伶,為每一個線程維護一個獨立的變量副本蚂且,每個線程的私有變量统锤。通過ThreadLocal可以將對象的可見范圍限制在同一個線程內毛俏。

一個誤區(qū)

不要將synchronized與ThreadLocal進行對比,sysnchronized是一種互斥同步機制饲窿,是為了保證在多線程環(huán)境下對于共享資源的正確訪問拧抖。而ThreadLocal是提供一個“線程級”的變量作用域(線程內的static變量),它是一種線程封閉(每個線程獨享變量)技術免绿,更直白點講唧席,ThreadLocal可以理解為將對象的作用范圍限制在一個線程上下文中,使得變量的作用域為“線程級”嘲驾。

再簡單點總結淌哟,Synchronized用于線程間的數據共享,而ThreadLocal則用于線程間的數據隔離辽故。

沒有ThreadLocal的時候徒仓,一個線程在其生命周期內,可能穿過多個層級誊垢,多個方法掉弛,如果有個對象需要在此線程周期內多次調用症见,且是跨層級的(線程內共享),通常的做法是通過參數進行傳遞殃饿;而ThreadLocal將變量綁定在線程上谋作,在一個線程周期內,無論“你身處何地”乎芳,只需通過其提供的get方法就可輕松獲取到對象遵蚜。極大地提高了對于“線程級變量”的訪問便利性。

用法:

一般把ThreadLocal變量作為單獨的public static變量

通過set(...)存入數據奈惑,通過get()獲取數據

解決get返回null問題

第一次使用get都會返回null吭净,可以繼承Threadlocal再覆蓋initialValue方法可以解決

private static final ThreadLocal<Integer> threadId = 
    new ThreadLocal<Integer>(){
       @Override
       protected Integer initialValue(){
           return 1;
       }
    }

看看set源碼

public void set(T value) {
    Thread t = Thread.currentThread()   //首先獲取當前線程對象
    ThreadLocalMap map = getMap(t);     //獲取該線程對象的ThreadLocalMap
    if (map != null)
        map.set(this, value);
        //如果map不為空,執(zhí)行set操作肴甸,以當前threadLocal對象為key寂殉,實際存儲對象為value進行set操作
    else
        createMap(t, value);            //如果map為空,則為該線程創(chuàng)建ThreadLocalMap
}

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;              //線程對象持有ThreadLocalMap的引用
}

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

從上面代碼可以看出來原在,ThreadLocal是通過在Thread里面保存了一個ThreadLocalMap才產生了全局線程私有變量不撑,在Thread中,也確實有這么一行

ThreadLocal.ThreadLocalMap threadLocals = null;

這樣子就大概知道ThreadLocal的設計思路了:

  1. ThreadLocal作為變量訪問的入口
  2. 每個Thread含有一個ThreadLocalMap對象晤斩,這個ThreadLocalMap持有對象的引用
  3. ThreadLocalMap以當前的threadlocal對象為key焕檬,以真正的存儲對象為value。get時通過threadlocal實例就可以找到綁定在當前線程上的對象澳泵。

看上去似乎變?yōu)镸ap<Thread,T>這種形式會自然些实愚,一個線程對應一個存儲對象。ThreadLocal這樣設計的目的主要有兩個:

  1. 保證當前線程結束時相關對象能盡快被回收兔辅,把value放在了線程當中腊敲,這樣線程死亡,value隨之回收
  2. ThreadLocalMap中的元素會大大減少维苔,而map過大更容易造成哈希沖突而導致性能變差

get源碼

public T get() {
    Thread t = Thread.currentThread();  //首先獲取當前線程
    ThreadLocalMap map = getMap(t);     //獲取線程的map對象
    if (map != null) {      //如果map不為空碰辅,以threadlocal實例為key獲取到對應Entry,然后從Entry中取出對象即可介时。
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null)
            return (T)e.value;
    }
    return setInitialValue();
    //如果map為空没宾,也就是第一次沒有調用set直接get(或者調用過set,又調用了remove)時沸柔,為其設定初始值
}

private T setInitialValue() {
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}

關于ThreadLocal的使用問題

如果我們為一個線程關聯的對象是“完全獨享”的循衰,也就是每個線程擁有一整套的新的棧中的對象引用+堆中的對象,那么這種情況下是真正的線程獨享變量”褐澎,相當于一種深度拷貝会钝,每個線程自己玩自己的,對該對象做任何的操作也不會對別的線程有任何影響工三。(對于對象過大的時候迁酸,如果每個線程有一個深拷貝先鱼,開銷很大)

另一種更普遍的情況,所謂的獨享變量副本奸鬓,其實也就是每個線程都擁有一個獨立的對象引用焙畔,而堆中的對象還是線程間共享的,這種情況下全蝶,自然還是會涉及到對共享資源的訪問操作闹蒜,依然會有線程不安全的風險寺枉。所以說抑淫,ThreadLocal無法解決線程安全問題。

為何threadLocals的類型ThreadLocalMap的鍵值為ThreadLocal對象姥闪,因為每個線程中可有多個threadLocal變量

另外還有一個坑

當使用不當時始苇,其作用域范圍使用者可能不明確。

解釋一下:線程如果運行完筐喳,也就是run方法執(zhí)行完了催式,線程注銷,ThreadLocal被回收避归;可是荣月,還有一種更加常見的情況,線程池梳毙!線程可能不會立馬注銷哺窄,也就是ThreadLocal不會被回收,如果ThreadLocal中包裝了集合類或復雜對象账锹,那內部集合類和復雜對象鎖占用的內存可能會膨脹

使用ThreadLocal的場景

最常見的ThreadLocal使用場景為 用來解決 數據庫連接萌业、Session管理等。

以Spring為例奸柬,Spring的事務管理器通過AOP切入業(yè)務代碼生年,在進入業(yè)務代碼前,會依據相應的事務管理器提取出相應的事務對象廓奕,假如事務管理器是DataSourceTransactionManager抱婉,就會從DataSource中獲取一個連接對象,通過一定的包裝后將其保存在ThreadLocal中桌粉。而且Spring也將DataSource進行了包裝授段,重寫了當中的getConnection()方法,或者說該方法的返回將由Spring來控制番甩,這樣Spring就能讓線程內多次獲取到的Connection對象是同一個侵贵。通過將連接對象保存在線程內部,不論什么時候都能拿到缘薛,此時Spring很清楚什么時候回收這個連接窍育,也就是很清楚什么時候從ThreadLocal中刪除這個元素

再來看看InheritableThreadLocal

作用:

顯然卡睦,在原本的ThreadLocal使用上,如果父線程創(chuàng)建了子線程漱抓,父線程是沒辦法把當前擁有的ThreadLocal傳遞給子線程的表锻,為何,在get()可以看得到乞娄,

public T get() {
    Thread t = Thread.currentThread();  //首先獲取當前線程
    ThreadLocalMap map = getMap(t); 
    if (map != null) { 
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null)
            return (T)e.value;
    }
    return setInitialValue();
}

第一步獲取當前線程瞬逊,而當前線程為子線程,子線程有自己的ThreadLocalMap仪或,跟父線程毫無關系确镊,這個地方可以從2點佐證:1. ThreadLocal的目的是為了使得每個線程有自己的獨立私有變量

  1. Thread的創(chuàng)建會調用init(...),當中不會為子線程繼承ThreadLocal(這里詳細可見Thread的init()函數)

有些時候需要使得父線程的一些變量繼承給子線程范删,這時候就出現了InheritableThreadLocal蕾域,

原理

用法跟ThreadLocal一樣,所以就只講講InheritableThreadLocal的實現到旦,直接上源碼

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    protected T childValue(T parentValue) {
        return parentValue;
    }
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

看樣子旨巷,這里的關鍵在于getMap這個地方,由于是繼承添忘,所以會使用覆蓋的方法采呐,原本的getMap(Thread t)是長這樣的

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;  
}

只是返回的map不同,InheritableThreadLocal返回的map是從父線程繼承下來的搁骑,但這是在什么時候設置的斧吐?最重點的地方到了,就是在每個Thread創(chuàng)建的時候都會調用的init()方法靶病,這里只貼出跟

private void init(ThreadGroup g, Runnable target, 
            String name, long stackSize, AccessControlContext acc) {
    ......    
    if (inheritThreadLocals && parent.inheritableThreadLocals != null)        
        this.inheritableThreadLocals = 
            ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    ......    
}

來到了ThreadLocal的方法

static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
    return new ThreadLocalMap(parentMap);       //這里把parentMap給復制了一遍
}

到此

參考:

http://www.cnblogs.com/dolphin0520/p/3920407.html
https://blog.csdn.net/ni357103403/article/details/51970748
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末会通,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子娄周,更是在濱河造成了極大的恐慌涕侈,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,888評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件煤辨,死亡現場離奇詭異裳涛,居然都是意外死亡,警方通過查閱死者的電腦和手機众辨,發(fā)現死者居然都...
    沈念sama閱讀 94,677評論 3 399
  • 文/潘曉璐 我一進店門端三,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人鹃彻,你說我怎么就攤上這事郊闯。” “怎么了?”我有些...
    開封第一講書人閱讀 168,386評論 0 360
  • 文/不壞的土叔 我叫張陵团赁,是天一觀的道長育拨。 經常有香客問我,道長欢摄,這世上最難降的妖魔是什么熬丧? 我笑而不...
    開封第一講書人閱讀 59,726評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮怀挠,結果婚禮上析蝴,老公的妹妹穿的比我還像新娘。我一直安慰自己绿淋,他們只是感情好闷畸,可當我...
    茶點故事閱讀 68,729評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著躬它,像睡著了一般腾啥。 火紅的嫁衣襯著肌膚如雪东涡。 梳的紋絲不亂的頭發(fā)上冯吓,一...
    開封第一講書人閱讀 52,337評論 1 310
  • 那天,我揣著相機與錄音疮跑,去河邊找鬼组贺。 笑死,一個胖子當著我的面吹牛祖娘,可吹牛的內容都是我干的失尖。 我是一名探鬼主播,決...
    沈念sama閱讀 40,902評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼渐苏,長吁一口氣:“原來是場噩夢啊……” “哼掀潮!你這毒婦竟也來了?” 一聲冷哼從身側響起琼富,我...
    開封第一講書人閱讀 39,807評論 0 276
  • 序言:老撾萬榮一對情侶失蹤仪吧,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后鞠眉,有當地人在樹林里發(fā)現了一具尸體薯鼠,經...
    沈念sama閱讀 46,349評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,439評論 3 340
  • 正文 我和宋清朗相戀三年械蹋,在試婚紗的時候發(fā)現自己被綠了出皇。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,567評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡哗戈,死狀恐怖郊艘,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤纱注,帶...
    沈念sama閱讀 36,242評論 5 350
  • 正文 年R本政府宣布步做,位于F島的核電站,受9級特大地震影響奈附,放射性物質發(fā)生泄漏全度。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,933評論 3 334
  • 文/蒙蒙 一斥滤、第九天 我趴在偏房一處隱蔽的房頂上張望将鸵。 院中可真熱鬧,春花似錦佑颇、人聲如沸顶掉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,420評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽痒筒。三九已至,卻和暖如春茬贵,著一層夾襖步出監(jiān)牢的瞬間簿透,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,531評論 1 272
  • 我被黑心中介騙來泰國打工解藻, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留老充,地道東北人。 一個月前我還...
    沈念sama閱讀 48,995評論 3 377
  • 正文 我出身青樓螟左,卻偏偏與公主長得像啡浊,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子胶背,可洞房花燭夜當晚...
    茶點故事閱讀 45,585評論 2 359

推薦閱讀更多精彩內容