套路之ThreadLocal

理解ThreadLocal

我們先來討論一下打架策略的問題,然后再來看看什么是ThreadLocal吧楣铁。如果你遇到三個歹徒玖雁,你會怎么辦呢? 假設(shè)你是個練家子,我想最容易想到的就是策略:以一敵三盖腕。為了不受傷害赫冬,你是輾轉(zhuǎn)挪騰,左躲右閃溃列,盡量保證同一時刻可以只對付一名歹徒劲厌,以便能夠全身而退。如果把三名歹徒對你的攻擊順序比作三個線程的話听隐,你實(shí)際上只在使用同步的方式來管理者三個線程對你的訪問补鼻。使用同步方式來管理多個線程對同一個對象的訪問是最常用的方式。但是雅任,你也看到了风范,你對付得很辛苦,稍有不慎沪么,就有可能受傷硼婿。但是如果你看過《火影忍者》的話,那么你還可以想到另外一種比較省事而且安全的策略:分身術(shù)禽车。你可以變出三個分身來分別同時對付三個歹徒寇漫。這實(shí)際上是實(shí)現(xiàn)線程安全的另外一種策略:線程封閉。也就是說你啊就別折騰了殉摔,我給每一個線程都分配了資源了州胳,就不需要去搶其他線程的資源了。其實(shí)這就是ThreadLocal的核心思想:通過避免線程間的共享來達(dá)到線程安全的目的钦勘。

分身術(shù)
分身術(shù)

所以同步和ThreadLocal在橫向上可能沒有直接的關(guān)系,但是從縱向上來看亚亲,它們都是為了保證線程安全彻采。只是實(shí)現(xiàn)手段不一樣而已。

ThreadLocal的實(shí)現(xiàn)原理

雖然是通過ThreadLocal來設(shè)置特定于各個線程的資源的捌归,但是ThreadLocal本身并不會保存這些特定的資源肛响。因?yàn)橘Y源是特定于線程的,自然是由每一個線程自己來管理了惜索。

ThreadLocal的實(shí)現(xiàn)原理:實(shí)線代表強(qiáng)引用,虛線代表弱引用.

每一個Thread對象都有一個ThreadLocal.ThreadLocalMap類型的名為ThreadLocals的實(shí)例變量特笋,它就是保持那些通過 ThreadLocal設(shè)置給這個線程的數(shù)據(jù)資源的地方。當(dāng)通過ThreadLocal的set(data)方法來設(shè)置數(shù)據(jù)的時候,ThreadLocal會首先獲取當(dāng)前線程的引用猎物,然后通過該應(yīng)用獲取當(dāng)前線程持有的threadLocals虎囚,最后以ThreadLocal最為Key,將要設(shè)置的數(shù)據(jù)設(shè)置到當(dāng)前線程,如下

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

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

實(shí)際上蔫磨,ThreadLocal就像是一個窗口淘讥,通過這個窗口,我們可以將特定于線程的數(shù)據(jù)資源綁定到當(dāng)前線程堤如,也可以通過這個窗口獲取綁定的數(shù)據(jù)資源蒲列,當(dāng)然也可以解除之前綁定到線程的數(shù)據(jù)資源。在整個線程的生命周期內(nèi)搀罢,我們都可以通過ThreadLocal這個窗口來與當(dāng)前線程打交道蝗岖。

為了更好地理解Thread和ThreadLocal之間的關(guān)系的,我們不妨假設(shè)城市的公交系統(tǒng)榔至。城市中的各條公交線路就好像我們系統(tǒng)中的每一個線程抵赢,在每條公交線路上,會有相應(yīng)的公交車輛洛退,這些公交車輛就好像是Thread中的thredLocals瓣俯,用來運(yùn)輸特定于該條線路的乘客(數(shù)據(jù)資源),為了乘客可以上車或者下車兵怯,各條公交線路在沿路上都設(shè)置了多個乘車點(diǎn)彩匕,而這些乘車點(diǎn)實(shí)際上就是ThreadLocal。雖然同一個乘車點(diǎn)可能會有多條線路公用媒区,單在同一時間驼仪,乘車只會搭乘他要乘坐并且當(dāng)前經(jīng)過的公交車。這與ThreadLocal和Thread的關(guān)系是相似的袜漩,雖然同一個ThreadLocal可以為多個線程指定數(shù)據(jù)資源绪爸,但只會將數(shù)據(jù)綁定到當(dāng)前線程。


ThreadLocal可能引起的內(nèi)存泄露

threadlocal里面使用了一個存在弱引用的map,當(dāng)釋放掉threadlocal的強(qiáng)引用以后,map里面的value卻沒有被回收.而這塊value永遠(yuǎn)不會被訪問到了. 所以存在著內(nèi)存泄露. 最好的做法是將調(diào)用threadlocal的remove方法

每個thread中都存在一個map, map的類型是ThreadLocal.ThreadLocalMap. Map中的key為一個threadlocal實(shí)例. 這個Map的確使用了弱引用,不過弱引用只是針對key. 每個key都弱引用指向threadlocal. 當(dāng)把threadlocal實(shí)例置為null以后,沒有任何強(qiáng)引用指向threadlocal實(shí)例,所以threadlocal將會被gc回收. 但是,我們的value卻不能回收,因?yàn)榇嬖谝粭l從current thread連接過來的強(qiáng)引用. 只有當(dāng)前thread結(jié)束以后, current thread就不會存在棧中,強(qiáng)引用斷開, Current Thread, Map, value將全部被GC回收.

所以得出一個結(jié)論就是只要這個線程對象被gc回收宙攻,就不會出現(xiàn)內(nèi)存泄露奠货,但在threadLocal設(shè)為null和線程結(jié)束這段時間不會被回收的,就發(fā)生了我們認(rèn)為的內(nèi)存泄露座掘。其實(shí)這是一個對概念理解的不一致递惋,也沒什么好爭論的。最要命的是線程對象不被回收的情況溢陪,這就發(fā)生了真正意義上的內(nèi)存泄露萍虽。比如使用線程池的時候,線程結(jié)束是不會銷毀的形真,會再次使用的杉编。就可能出現(xiàn)內(nèi)存泄露。

PS.Java為了最小化減少內(nèi)存泄露的可能性和影響,在ThreadLocal的get,set的時候都會清除線程Map里所有key為null的value邓馒。所以最怕的情況就是嘶朱,threadLocal對象設(shè)null了,開始發(fā)生“內(nèi)存泄露”绒净,然后使用線程池见咒,這個線程結(jié)束,線程放回線程池中不銷毀挂疆,這個線程一直不被使用改览,或者分配使用了又不再調(diào)用get,set方法,那么這個期間就會發(fā)生真正的內(nèi)存泄露缤言。

ThreadLocal的使用場景

首先我們可以從兩個方面來看待并使用ThreadLocal

  • 橫向上看宝当,我們更看重于ThreadLocal跨越多個線程的能力。為了以更加簡單的方式來管理應(yīng)用程序的線程安全胆萧,ThreadLocal干脆將沒有必要共享的對象不共享庆揩,直接為每一個線程分配一份各自特定的數(shù)據(jù)資源。
  • 縱向上看跌穗,我們更側(cè)重于ThreadLocal能夠?qū)?shù)據(jù)資源綁定到當(dāng)前線程的能力订晌。這樣我們通過ThreadLocal設(shè)置的特定于各個線程的數(shù)據(jù)資源,可以隨著所在線程的執(zhí)行流程“隨波逐流”蚌吸。

當(dāng)然锈拨,這兩個方面不是相互獨(dú)立的,更多時候是相互依存羹唠,緊密結(jié)合的奕枢。在充分發(fā)揮ThreadLocal兩方面的能力的基礎(chǔ)上,我們可以總結(jié)出ThreadLocal的以下應(yīng)用場景佩微。

  • 管理應(yīng)用程序的線程安全
    對于某些有狀態(tài)或者非線程安全的對象缝彬,我們可以在多線程程序中為每一個線程分配對應(yīng)的副本,而不是讓多個線程共享該類型的對象哺眯,而從避免了需要協(xié)調(diào)多個線程對這些對象進(jìn)行訪問的“危險(xiǎn)”的工作谷浅。數(shù)據(jù)庫連接就是這一類對象。所以我們可以為每一個線程分配一個獨(dú)立的Connection對象奶卓,從而避免了單一Connection對象的爭用一疯。而且,在JDBC中一個Connection就對應(yīng)了一個事務(wù)寝杖,如果所有的線程都公用一個connection的話违施,那么整個事務(wù)管理就失控了互纯。
  • 線程內(nèi)的數(shù)據(jù)傳遞
    采用ThreadLocal來進(jìn)行當(dāng)前流程的參數(shù)傳遞瑟幕,可以避免耦合性很強(qiáng)的方法參數(shù)形式的傳遞方式。但這有些像是讓數(shù)據(jù)隨著“暗流”漂泊的意思。一旦處理不當(dāng)就會出現(xiàn)“觸礁”之類的事故只盹。比如辣往,資源沒有進(jìn)行合理的清理導(dǎo)致心痛行為異常。所以通常應(yīng)該通過一組框架來規(guī)范并屏蔽對ThreadLocal的直接操作殖卑,盡量避免應(yīng)用代碼的直接接觸
  • 某些情況下的性能優(yōu)化

具體的使用場景可以在我下一篇博文中查看站削,這一篇暫時就到這。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末孵稽,一起剝皮案震驚了整個濱河市许起,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌菩鲜,老刑警劉巖园细,帶你破解...
    沈念sama閱讀 219,110評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異接校,居然都是意外死亡猛频,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評論 3 395
  • 文/潘曉璐 我一進(jìn)店門蛛勉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鹿寻,“玉大人,你說我怎么就攤上這事诽凌≌毖” “怎么了?”我有些...
    開封第一講書人閱讀 165,474評論 0 356
  • 文/不壞的土叔 我叫張陵皿淋,是天一觀的道長招刹。 經(jīng)常有香客問我,道長窝趣,這世上最難降的妖魔是什么疯暑? 我笑而不...
    開封第一講書人閱讀 58,881評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮哑舒,結(jié)果婚禮上妇拯,老公的妹妹穿的比我還像新娘。我一直安慰自己洗鸵,他們只是感情好越锈,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,902評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著膘滨,像睡著了一般甘凭。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上火邓,一...
    開封第一講書人閱讀 51,698評論 1 305
  • 那天丹弱,我揣著相機(jī)與錄音德撬,去河邊找鬼。 笑死躲胳,一個胖子當(dāng)著我的面吹牛蜓洪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播坯苹,決...
    沈念sama閱讀 40,418評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼隆檀,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了粹湃?” 一聲冷哼從身側(cè)響起恐仑,我...
    開封第一講書人閱讀 39,332評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎为鳄,沒想到半個月后菊霜,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,796評論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡济赎,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,968評論 3 337
  • 正文 我和宋清朗相戀三年鉴逞,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片司训。...
    茶點(diǎn)故事閱讀 40,110評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡构捡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出壳猜,到底是詐尸還是另有隱情勾徽,我是刑警寧澤,帶...
    沈念sama閱讀 35,792評論 5 346
  • 正文 年R本政府宣布统扳,位于F島的核電站喘帚,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏咒钟。R本人自食惡果不足惜吹由,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,455評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望朱嘴。 院中可真熱鬧倾鲫,春花似錦、人聲如沸萍嬉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,003評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽壤追。三九已至磕道,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間行冰,已是汗流浹背溺蕉。 一陣腳步聲響...
    開封第一講書人閱讀 33,130評論 1 272
  • 我被黑心中介騙來泰國打工贯卦, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人焙贷。 一個月前我還...
    沈念sama閱讀 48,348評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像贿堰,于是被迫代替她去往敵國和親辙芍。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,047評論 2 355

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

  • 從三月份找實(shí)習(xí)到現(xiàn)在羹与,面了一些公司故硅,掛了不少,但最終還是拿到小米纵搁、百度吃衅、阿里、京東腾誉、新浪徘层、CVTE、樂視家的研發(fā)崗...
    時芥藍(lán)閱讀 42,253評論 11 349
  • Android Handler機(jī)制系列文章整體內(nèi)容如下: Android Handler機(jī)制1之ThreadAnd...
    隔壁老李頭閱讀 7,637評論 4 30
  • 前言 ThreadLocal很多同學(xué)都搞不懂是什么東西利职,可以用來干嘛趣效。但面試時卻又經(jīng)常問到,所以這次我和大家一起學(xué)...
    liangzzz閱讀 12,452評論 14 228
  • 一猪贪、多線程 說明下線程的狀態(tài) java中的線程一共有 5 種狀態(tài)跷敬。 NEW:這種情況指的是,通過 New 關(guān)鍵字創(chuàng)...
    Java旅行者閱讀 4,680評論 0 44
  • 二十二年前你不加思索毫無選擇地來到我和你爸即將失業(yè)而又無任何資產(chǎn)的工人家庭热押。 隨著你聲啼哭把你爸初為人父的喜悅勁頭...
    上善若水_f607閱讀 247評論 0 4