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

所以同步和ThreadLocal在橫向上可能沒有直接的關(guān)系,但是從縱向上來看亚亲,它們都是為了保證線程安全彻采。只是實(shí)現(xiàn)手段不一樣而已。
ThreadLocal的實(shí)現(xiàn)原理
雖然是通過ThreadLocal來設(shè)置特定于各個線程的資源的捌归,但是ThreadLocal本身并不會保存這些特定的資源肛响。因?yàn)橘Y源是特定于線程的,自然是由每一個線程自己來管理了惜索。
每一個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)化
具體的使用場景可以在我下一篇博文中查看站削,這一篇暫時就到這。