ThreadLocal 線程本地變量,算是Java開發(fā)中比較常用的API了,今天我們來一探究竟
使用場景
ThreadLocal 適用于每個(gè)線程需要自己獨(dú)立的實(shí)例且該實(shí)例需要在多個(gè)方法中被使用,也就是變量在線程間隔離,而在同一線程共享的場景。例如管理Connection,我們希望每個(gè)線程只使用一個(gè)Connection實(shí)例,這個(gè)時(shí)候用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中存儲(chǔ)的對象
threadLocal.get();
// 清除ThreadLocal中數(shù)據(jù)
threadLocal.remove();
}
}
還有之前寫過的一篇?jiǎng)討B(tài)切換數(shù)據(jù)源 http://www.itdecent.cn/p/0a485c965b8b,AOP 通過 ThreadLocal 保存當(dāng)前線程需要訪問的數(shù)據(jù)源的key,AbstractRoutingDataSource 再通過 ThreadLocal 中的數(shù)據(jù)切換到指定的數(shù)據(jù)源,對業(yè)務(wù)代碼毫無入侵
原理
在我們了解了如何使用之后,來看下 ThreadLocal 是如何實(shí)現(xiàn)的
ThreadLocal.get()
我們從get方法來分析,可以看到方法中獲取當(dāng)前線程,并通過當(dāng)前線程得到一個(gè) ThreadLocalMap,我們可以暫時(shí)把這個(gè)ThreadLocalMap 理解為我們熟悉的HashMap,然后通過 this(當(dāng)前ThreadLocal對象)作為key,從Map中獲取Entry

我們再來看下,ThreadLocalMap 以及ThreadLocalMap.Entry 中的核心成員變量,ThreadLocalMap 中實(shí)現(xiàn)了一個(gè)簡單的hash表

看到這里你可能還不是很清晰,結(jié)合下面這張圖理解一下,每個(gè)線程(Thread對象)中有一個(gè)ThreadLocalMap,使線程之間的數(shù)據(jù)天然隔離,ThreadLocalMap 有一張hash表 Entry[],每個(gè) Entry 中對應(yīng)存儲(chǔ)著一個(gè)ThreadLocal實(shí)例 - value,這樣使得不同的ThreadLocal 對象之間也形成了隔離

ThreadLocalMap 中的hash表
我們通過 ThreadLocalMap.set() 來了解下內(nèi)部的hash表是如何實(shí)現(xiàn)的

線性探測是指當(dāng)發(fā)生hash沖突時(shí),利用固定的算法尋找一定步長的下個(gè)位置(ThreadLocal中發(fā)生hash沖突時(shí),index+1),依次判斷,直至找到能夠存放的位置
如果線程中操作了大量的 ThreadLocal 對象,勢必會(huì)造成hash沖突,這是沒有必要的性能開銷,如果可以的話,我們可以只保留一個(gè)ThreadLocal對象
關(guān)于 ThreadLocal 的一些思考
- 為什么要使用弱引用
圖3中,我們看到hash表中會(huì)出現(xiàn) key == null的Entry,這是因?yàn)?ThreadLocalMap.Entry 的key (Entry 對ThreadLocal設(shè)置了弱引用,可以回顧一下圖2)
弱引用的對象擁有更短暫的生命周期。在GC時(shí),一旦發(fā)現(xiàn)了對象只具有弱引用,這個(gè)對象一定被回收
這么做的原因:如果ThreadLocal 對象需要被回收時(shí)(此時(shí)并沒有調(diào)用ThreadLocal.remove),線程中的ThreadLocalMap 一直強(qiáng)引用著 ThreadLocal對象,這會(huì)讓 ThreadLocal對象 以及對應(yīng)的value對象內(nèi)存無法釋放,導(dǎo)致內(nèi)存泄漏。這算是ThreadLocal的一種容錯(cuò)機(jī)制,這樣做使得了ThreadLocal對象得到了回收,但是value的內(nèi)存并沒有釋放,所以ThreadLocalMap 的get、set方法中都會(huì)去嘗試清理ThreadLocal已經(jīng)被回收的entry。
- 使用過后不及時(shí)remove會(huì)怎么樣
很多博客中都強(qiáng)調(diào)了,ThreadLocal.remove的重要性。舉個(gè)例子,我們新啟了一個(gè)線程在這個(gè)線程中使用了ThreadLocal,我們并沒有調(diào)用remove,這會(huì)導(dǎo)致存儲(chǔ)的value對象一直沒有辦法被回收,直到線程被銷毀
- 線程池中也需要remove嗎
以web線程池為例,如果每次都在過濾器中操作同一個(gè)ThreadLocal.set,然后業(yè)務(wù)代碼中g(shù)et,似乎沒什么問題。計(jì)算出的hash值都是一樣的,槽位也是一樣的會(huì)覆蓋上一次的值。確實(shí)業(yè)務(wù)不會(huì)有問題,但是還是推薦大家在使用完之后remove,因?yàn)檫@樣會(huì)讓無用的value對象早點(diǎn)被回收,在很多java源碼中都會(huì)看到,對一些不再使用的對象進(jìn)行如下的help GC操作
object = null // help GC
所以我們也需要讓無用的對象失去引用,幫助GC
- 綜上所述
ThreadLocal 使用過后要及時(shí)remove,幫助JVM釋放內(nèi)存