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

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

圖1

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

圖2

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

圖片來自網(wǎng)絡(luò)
ThreadLocalMap 中的hash表

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

圖3

線性探測是指當(dāng)發(fā)生hash沖突時(shí),利用固定的算法尋找一定步長的下個(gè)位置(ThreadLocal中發(fā)生hash沖突時(shí),index+1),依次判斷,直至找到能夠存放的位置

如果線程中操作了大量的 ThreadLocal 對象,勢必會(huì)造成hash沖突,這是沒有必要的性能開銷,如果可以的話,我們可以只保留一個(gè)ThreadLocal對象

關(guān)于 ThreadLocal 的一些思考

  1. 為什么要使用弱引用

圖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。

  1. 使用過后不及時(shí)remove會(huì)怎么樣

很多博客中都強(qiáng)調(diào)了,ThreadLocal.remove的重要性。舉個(gè)例子,我們新啟了一個(gè)線程在這個(gè)線程中使用了ThreadLocal,我們并沒有調(diào)用remove,這會(huì)導(dǎo)致存儲(chǔ)的value對象一直沒有辦法被回收,直到線程被銷毀

  1. 線程池中也需要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

  1. 綜上所述

ThreadLocal 使用過后要及時(shí)remove,幫助JVM釋放內(nèi)存

參考

http://www.itdecent.cn/p/98b68c97df9b

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容