ThreadLocal

這篇文章寫的好了http://duanqz.github.io/2018-03-15-Java-ThreadLocal

同時這篇文章關(guān)于內(nèi)存泄漏講的挺好的http://www.importnew.com/22039.html

http://www.iteye.com/topic/103804

ThreadLocal,在沒有任何背景知識的情況下,我們從英文單詞的意思上理解它:

Thread:跟線程相關(guān)。Java語言中,表示線程的類就是Thread鹅颊,是程序最小的執(zhí)行單元,多個線程可以并發(fā)執(zhí)行。

Local:本地财松、局部纱控,與之相對的概念就是遠(yuǎn)程舶掖、全局眨攘。Java語言中,通常用Local表示局部變量龟虎。

這兩個概念一組合鲤妥,拼成了英文單詞ThreadLocal棉安,線程局部铸抑?局部線程蒲赂?究竟要表達(dá)什么意思木蹬,為什么不叫LocalThread镊叁,完全找不著北啊蛔添!

筆者搜羅了網(wǎng)上對ThreadLocal的一些解讀:

ThreadLocal為解決多線程程序的并發(fā)問題提供了一種新的思路夸溶。使用這個工具類可以很簡潔地編寫出優(yōu)美的多線程程序扫皱,ThreadLocal并不是一個Thread,而是Thread的局部變量段多,把它命名為ThreadLocalVariable更容易讓人理解一些

ThreadLocal并不是用來并發(fā)控制訪問一個共同對象进苍,而是為了給每個線程分配一個只屬于該線程的變量。它的功用非常簡單鸭叙,就是為每一個使用該變量的線程都提供一個變量值的副本觉啊,是每一個線程都可以獨(dú)立地改變自己的副本,而不會和其它線程的副本沖突沈贝,實(shí)現(xiàn)線程間的數(shù)據(jù)隔離杠人。從線程的角度看,就好像每一個線程都完全擁有該變量

ThreadLocal類用來提供線程內(nèi)部的局部變量。這種變量在多線程環(huán)境下訪問(通過get或set方法訪問)時能保證各個線程里的變量相對獨(dú)立于其他線程內(nèi)的變量嗡善。ThreadLocal實(shí)例通常來說都是private static類型的市俊,用于關(guān)聯(lián)線程和線程的上下文

引入ThreadLocal的初衷是為了提供線程內(nèi)的局部變量,而不是為了解決共享對象的多線程訪問問題滤奈。實(shí)際上摆昧,ThreadLocal根本就不能解決共享對象的多線程訪問問題

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

ThreadLocal的實(shí)現(xiàn)是這樣的:每個Thread維護(hù)一個ThreadLocalMap映射表,這個映射表的key是ThreadLocal實(shí)例本身蜒程,value是真正需要存儲的Object绅你。

也就是說ThreadLocal本身并不存儲值,它只是作為一個key來讓線程從ThreadLocalMap獲取value昭躺。值得注意的是圖中的虛線忌锯,表示ThreadLocalMap是使用ThreadLocal的弱引用作為Key的,弱引用的對象在 GC 時會被回收领炫。

線程類Thread中有一個類型為ThreadLocalMap的變量為threadLocals

ThreadLocalMap是一個映射表偶垮,內(nèi)部實(shí)現(xiàn)是一個數(shù)組,每一個元素的類型為Entry

Entry就是一個鍵值對(Key-Value Pair)帝洪,其?Key?就是ThreadLocal似舵,其?Value?可以是任何對象

1.1 ThreadLocal的主要接口

set(),表示要往當(dāng)前線程中設(shè)置“本地變量”葱峡,最終的結(jié)果是將變量設(shè)置到了線程的映射表砚哗。

get(),表示要從當(dāng)前線程中取出“本地變量”砰奕,最終的結(jié)果是在當(dāng)前線程的映射表中蛛芥,以調(diào)用get()方法的ThreadLocal對象為Key,查詢出對應(yīng)的Value军援。

ThreadLocal的set()和get()方法的主體邏輯算是比較簡單了仅淑,圍繞主體邏輯,還做了一些特殊處理胸哥,譬如:線程中的映射表還未初始化時涯竟,調(diào)用createMap()進(jìn)行初始化;在映射表中沒有獲取到Value時烘嘱,通過setInitialValue()設(shè)置一個初始值昆禽,這種場景下,只需要實(shí)現(xiàn)initialValue()函數(shù)就可以了蝇庭,這種ThreadLocal的使用方式很常見醉鳖。本文不再展開這些細(xì)枝末節(jié)的邏輯,讀者自行閱讀源碼即可哮内。

1.2 ThreadLocalMap映射表

ThreadLocal并不是一個存儲容器盗棵,往ThreadLocal中讀(get)和寫(set)數(shù)據(jù)壮韭,其實(shí)都是將數(shù)據(jù)保存到了每個線程自己的存儲空間。

線程中的存儲空間是一個映射表(ThreadLocalMap)纹因,TheadLocal其實(shí)就是這個映射表每一項(xiàng)的Key喷屋,通過ThreadLocal讀寫數(shù)據(jù),其實(shí)就是通過Key在一個映射表中讀寫數(shù)據(jù)瞭恰。

上文中圖示中屯曹,我們見過映射表的結(jié)構(gòu),它是一個名為table的數(shù)組惊畏,每一個元素都是Entry對象恶耽,而Entry對象包含key和value兩個屬性,其代碼如下所示:

ThreadLocalMap的Entry是WeakReference的子類颜启,這樣能保證線程中的映射表的每一個Entry可以被垃圾回收偷俭,而不至于發(fā)生內(nèi)存泄露。因?yàn)?b>ThreadLocal作為全局的Key缰盏,其生命周期很可能比一個線程要長涌萤,如果Entry是一個強(qiáng)引用,那么線程對象就一直持有ThreadLocal的引用口猜,而不能被釋放负溪。隨著線程越來越多,這些不能被釋放的內(nèi)存也就越來越多暮的。

ThreadLocal作為映射表的Key笙以,需要具備唯一的標(biāo)識,每創(chuàng)建一個新的ThreadLocal冻辩,這個標(biāo)識就變的跟之前不一樣了。 如何保證每一個ThreadLocal的唯一性呢拆祈?

ThreadLocal內(nèi)部有一個名為threadLocalHashCode的變量恨闪,每創(chuàng)建一個新的ThreadLocal對象,這個變量的值就會增加0x61c88647放坏。 正是因?yàn)橛羞@么一個神奇的數(shù)字咙咽,它能夠保證生成的Hash值可以均勻的分布在0~(2^N-1)之間,N是數(shù)組長度淤年。 更多關(guān)于數(shù)字0x61c88647钧敞,可以參考Why 0x61c88647?

2. 使用場景

下面來看一個hibernate中典型的ThreadLocal的應(yīng)用:?

Java代碼?

private?static?final?ThreadLocal?threadSession?=?new?ThreadLocal();??

public?static?Session?getSession()?throws?InfrastructureException?{??

????Session?s?=?(Session)?threadSession.get();??

try?{??

if?(s?==?null)?{??

????????????s?=?getSessionFactory().openSession();??

????????????threadSession.set(s);??

????????}??

}catch?(HibernateException?ex)?{??

throw?new?InfrastructureException(ex);??

????}??

return?s;??

}??

可以看到在getSession()方法中,首先判斷當(dāng)前線程中有沒有放進(jìn)去session麸粮,如果還沒有溉苛,那么通過sessionFactory().openSession()來創(chuàng)建一個session,再將session set到線程中弄诲,實(shí)際是放到當(dāng)前線程的ThreadLocalMap這個map中愚战,這時,對于這個session的唯一引用就是當(dāng)前線程中的那個ThreadLocalMap(下面會講到),而threadSession作為這個值的key寂玲,要取得這個session可以通過threadSession.get()來得到塔插,里面執(zhí)行的操作實(shí)際是先取得當(dāng)前線程中的ThreadLocalMap,然后將threadSession作為key將對應(yīng)的值取出拓哟。這個session相當(dāng)于線程的私有變量想许,而不是public的。 顯然断序,其他線程中是取不到這個session的伸刃,他們也只能取到自己的ThreadLocalMap中的東西

3.ThreadLocal容易導(dǎo)致內(nèi)存泄漏的問題

ThreadLocalMap使用ThreadLocal的弱引用作為key,如果一個ThreadLocal沒有外部強(qiáng)引用來引用它逢倍,那么系統(tǒng) GC 的時候捧颅,這個ThreadLocal勢必會被回收,這樣一來较雕,ThreadLocalMap中就會出現(xiàn)key為null的Entry碉哑,就沒有辦法訪問這些key為null的Entry的value,如果當(dāng)前線程再遲遲不結(jié)束的話亮蒋,這些key為null的Entry的value就會一直存在一條強(qiáng)引用鏈:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永遠(yuǎn)無法回收扣典,造成內(nèi)存泄漏。

其實(shí)慎玖,ThreadLocalMap的設(shè)計中已經(jīng)考慮到這種情況贮尖,也加上了一些防護(hù)措施:在ThreadLocal的get(),set(),remove()的時候都會清除線程ThreadLocalMap里所有key為null的value。

但是這些被動的預(yù)防措施并不能保證不會內(nèi)存泄漏:

使用static的ThreadLocal趁怔,延長了ThreadLocal的生命周期湿硝,可能導(dǎo)致的內(nèi)存泄漏(參考ThreadLocal 內(nèi)存泄露的實(shí)例分析)。

分配使用了ThreadLocal又不再調(diào)用get(),set(),remove()方法润努,那么就會導(dǎo)致內(nèi)存泄漏关斜。

為什么使用弱引用

從表面上看內(nèi)存泄漏的根源在于使用了弱引用。網(wǎng)上的文章大多著重分析ThreadLocal使用了弱引用會導(dǎo)致內(nèi)存泄漏铺浇,但是另一個問題也同樣值得思考:為什么使用弱引用而不是強(qiáng)引用痢畜?

下面我們分兩種情況討論:

key 使用強(qiáng)引用:引用的ThreadLocal的對象被回收了,但是ThreadLocalMap還持有ThreadLocal的強(qiáng)引用鳍侣,如果沒有手動刪除丁稀,ThreadLocal不會被回收,導(dǎo)致Entry內(nèi)存泄漏倚聚。

key 使用弱引用:引用的ThreadLocal的對象被回收了线衫,由于ThreadLocalMap持有ThreadLocal的弱引用,即使沒有手動刪除秉沼,ThreadLocal也會被回收桶雀。value在下一次ThreadLocalMap調(diào)用set,get矿酵,remove的時候會被清除。

比較兩種情況矗积,我們可以發(fā)現(xiàn):由于ThreadLocalMap的生命周期跟Thread一樣長全肮,如果都沒有手動刪除對應(yīng)key,都會導(dǎo)致內(nèi)存泄漏棘捣,但是使用弱引用可以多一層保障:弱引用ThreadLocal不會內(nèi)存泄漏辜腺,對應(yīng)的value在下一次ThreadLocalMap調(diào)用set,get,remove的時候會被清除

因此乍恐,ThreadLocal內(nèi)存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一樣長评疗,如果沒有手動刪除對應(yīng)key就會導(dǎo)致內(nèi)存泄漏,而不是因?yàn)槿跻谩?/p>

ThreadLocal 最佳實(shí)踐

綜合上面的分析茵烈,我們可以理解ThreadLocal內(nèi)存泄漏的前因后果百匆,那么怎么避免內(nèi)存泄漏呢?

每次使用完ThreadLocal呜投,都調(diào)用它的remove()方法加匈,清除數(shù)據(jù)。

在使用線程池的情況下仑荐,沒有及時清理ThreadLocal雕拼,不僅是內(nèi)存泄漏的問題,更嚴(yán)重的是可能導(dǎo)致業(yè)務(wù)邏輯出現(xiàn)問題粘招。所以啥寇,使用ThreadLocal就跟加鎖完要解鎖一樣,用完就清理洒扎。

4.總結(jié)

通過上述使用場景可以發(fā)現(xiàn)辑甜,ThreadLocal確實(shí)提供了一種編程手段,本來需要在線程中顯示聲明的局部變量逊笆,像是被ThreadLocal隱藏了起來栈戳,當(dāng)多個線程運(yùn)行起來時,每個線程都往相同的ThreadLocal中存取所需要的變量就可以了难裆,使用ThreadLocal存取的變量,就像是每個線程自己的局部變量镊掖,不受其他線程運(yùn)行狀態(tài)的影響乃戈。

通過ThreadLocal可以解決多線程讀共享數(shù)據(jù)的問題,因?yàn)楣蚕頂?shù)據(jù)會被復(fù)制到每個線程亩进,不需要加鎖便可同步訪問症虑。但ThreadLocal解決不了多線程寫共享數(shù)據(jù)的問題,因?yàn)槊總€線程寫的都是自己本線程的局部變量归薛,并沒將寫數(shù)據(jù)的結(jié)果同步到其他線程谍憔。理解了這一點(diǎn)匪蝙,才能理解所謂的:

ThreadLocal以空間換時間,提升多線程并發(fā)的效率习贫。什么意思呢逛球?每個線程都有一個ThreadLocalMap映射表,正是利用了這個映射表所占用的空間苫昌,使得多個線程都可以訪問自己的這片空間颤绕,不用擔(dān)心考慮線程同步問題,效率自然會高祟身。

ThreadLocal并不是為了解決共享數(shù)據(jù)的互斥寫問題奥务,而是通過一種編程手段,正好提供了并行讀的功能袜硫。什么意思呢氯葬?ThreadLocal并不是萬能的,它的設(shè)計初衷只是提供一個便利性婉陷,使得線程可以更為方便地使用局部變量帚称。

ThreadLocal提供了一種線程全域訪問功能,什么意思呢憨攒?一旦將一個對象添加到ThreadLocal中世杀,只要不移除它,那么肝集,在線程的生命周期內(nèi)的任何地方瞻坝,都可以通過ThreadLocal.get()方法拿到這個對象。有時候杏瞻,代碼邏輯比較復(fù)雜所刀,一個線程的代碼可能分散在很多地方,利用ThreadLocal這種便利性捞挥,就能簡化編程邏輯浮创。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市砌函,隨后出現(xiàn)的幾起案子斩披,更是在濱河造成了極大的恐慌,老刑警劉巖讹俊,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件垦沉,死亡現(xiàn)場離奇詭異,居然都是意外死亡仍劈,警方通過查閱死者的電腦和手機(jī)厕倍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來贩疙,“玉大人讹弯,你說我怎么就攤上這事况既。” “怎么了组民?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵棒仍,是天一觀的道長。 經(jīng)常有香客問我邪乍,道長降狠,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任庇楞,我火速辦了婚禮榜配,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘吕晌。我一直安慰自己蛋褥,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布睛驳。 她就那樣靜靜地躺著烙心,像睡著了一般。 火紅的嫁衣襯著肌膚如雪乏沸。 梳的紋絲不亂的頭發(fā)上淫茵,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天,我揣著相機(jī)與錄音蹬跃,去河邊找鬼匙瘪。 笑死,一個胖子當(dāng)著我的面吹牛蝶缀,可吹牛的內(nèi)容都是我干的丹喻。 我是一名探鬼主播,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼翁都,長吁一口氣:“原來是場噩夢啊……” “哼碍论!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起柄慰,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤鳍悠,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后坐搔,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體贼涩,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年薯蝎,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片谤绳。...
    茶點(diǎn)故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡占锯,死狀恐怖袒哥,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情消略,我是刑警寧澤堡称,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站艺演,受9級特大地震影響却紧,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜胎撤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一晓殊、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧伤提,春花似錦巫俺、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至舶沛,卻和暖如春嘹承,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背如庭。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工叹卷, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人柱彻。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓豪娜,卻偏偏與公主長得像,于是被迫代替她去往敵國和親哟楷。 傳聞我的和親對象是個殘疾皇子瘤载,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,697評論 2 351

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