ThreadLocal 線程本地變量技健,算是Java開發(fā)中比較常用的API了惰拱,今天我們來一探究竟
使用場景
ThreadLocal 適用于每個線程需要自己獨立的實例且該實例需要在多個方法中被使用啊送,也就是變量在線程間隔離馋没,而在同一線程共享的場景降传。例如管理Connection婆排,我們希望每個線程只使用一個Connection實例,這個時候用ThreadLocal就很合適腮猖。
public class ThreadLocalDemo {
private static final ThreadLocal<Object> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
threadLocal.set(new Object());
someMethod();
}
static void someMethod() {
// 獲取在threadLocal中存儲的對象
threadLocal.get();
// 清除ThreadLocal中數(shù)據(jù)
threadLocal.remove();
}
}
還有之前寫過的一篇動態(tài)切換數(shù)據(jù)源 http://www.reibang.com/p/0a485c965b8b澈缺,AOP 通過 ThreadLocal 保存當(dāng)前線程需要訪問的數(shù)據(jù)源的key姐赡,AbstractRoutingDataSource 再通過 ThreadLocal 中的數(shù)據(jù)切換到指定的數(shù)據(jù)源柠掂,對業(yè)務(wù)代碼毫無入侵
原理
在我們了解了如何使用之后涯贞,來看下 ThreadLocal 是如何實現(xiàn)的
ThreadLocal.get()
我們從get方法來分析,可以看到方法中獲取當(dāng)前線程,并通過當(dāng)前線程得到一個 ThreadLocalMap
姥饰,我們可以暫時把這個ThreadLocalMap 理解為我們熟悉的HashMap列粪,然后通過 this(當(dāng)前ThreadLocal對象)作為key岂座,從Map中獲取Entry
我們再來看下,ThreadLocalMap 以及ThreadLocalMap.Entry 中的核心成員變量钾恢,ThreadLocalMap 中實現(xiàn)了一個簡單的hash表
看到這里你可能還不是很清晰泉懦,結(jié)合下面這張圖理解一下,每個線程(Thread對象)中有一個ThreadLocalMap崩哩,使線程之間的數(shù)據(jù)天然隔離邓嘹,ThreadLocalMap 有一張hash表 Entry[]汹押,每個 Entry 中對應(yīng)存儲著一個ThreadLocal實例 - value鸯乃,這樣使得不同的ThreadLocal 對象之間也形成了隔離
ThreadLocalMap 中的hash表
我們通過 ThreadLocalMap.set() 來了解下內(nèi)部的hash表是如何實現(xiàn)的
線性探測是指當(dāng)發(fā)生hash沖突時鸟悴,利用固定的算法尋找一定步長的下個位置(ThreadLocal中發(fā)生hash沖突時奖年,index+1),依次判斷震贵,直至找到能夠存放的位置
如果線程中操作了大量的 ThreadLocal 對象猩系,勢必會造成hash沖突中燥,這是沒有必要的性能開銷,如果可以的話疗涉,我們可以只保留一個ThreadLocal對象
關(guān)于 ThreadLocal 的一些思考
- 為什么要使用弱引用
圖3中咱扣,我們看到hash表中會出現(xiàn) key == null
的Entry,這是因為 ThreadLocalMap.Entry 的key (Entry 對ThreadLocal設(shè)置了弱引用沪铭,可以回顧一下圖2)
弱引用的對象擁有更短暫的生命周期。在GC時火窒,一旦發(fā)現(xiàn)了對象只具有弱引用熏矿,這個對象一定被回收
這么做的原因:如果ThreadLocal 對象需要被回收時(此時并沒有調(diào)用ThreadLocal.remove)离钝,線程中的ThreadLocalMap 一直強引用著 ThreadLocal對象,這會讓 ThreadLocal對象 以及對應(yīng)的value對象內(nèi)存無法釋放慧域,導(dǎo)致內(nèi)存泄漏昔榴。這算是ThreadLocal的一種容錯機制碘橘,這樣做使得了ThreadLocal對象得到了回收痘拆,但是value的內(nèi)存并沒有釋放纺蛆,所以ThreadLocalMap 的get、set方法中都會去嘗試清理ThreadLocal已經(jīng)被回收的entry温峭。
- 使用過后不及時remove會怎么樣
很多博客中都強調(diào)了凤藏,ThreadLocal.remove的重要性清笨。舉個例子刃跛,我們新啟了一個線程在這個線程中使用了ThreadLocal苛萎,我們并沒有調(diào)用remove,這會導(dǎo)致存儲的value對象一直沒有辦法被回收蛙酪,直到線程被銷毀
- 線程池中也需要remove嗎
以web線程池為例,如果每次都在過濾器中操作同一個ThreadLocal.set凹蜂,然后業(yè)務(wù)代碼中g(shù)et玛痊,似乎沒什么問題狂打。計算出的hash值都是一樣的,槽位也是一樣的會覆蓋上一次的值对省。確實業(yè)務(wù)不會有問題蒿涎,但是還是推薦大家在使用完之后remove同仆,因為這樣會讓無用的value對象早點被回收俗批,在很多java源碼中都會看到岁忘,對一些不再使用的對象進(jìn)行如下的help GC操作
object = null // help GC
所以我們也需要讓無用的對象失去引用干像,幫助GC
- 綜上所述
ThreadLocal 使用過后要及時remove麻汰,幫助JVM釋放內(nèi)存