java中ThreadLocal的基本原理
源碼實(shí)現(xiàn)
一個(gè)線程內(nèi)可以存多個(gè)ThreadLocal對(duì)象,存儲(chǔ)的位置位于Thread的ThreadLocal.ThreadLocalMap變量,在Thread中有如下變量:
/* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocalMap是由ThreadLocal維護(hù)的靜態(tài)內(nèi)部類(lèi),正如代碼中注解所說(shuō)這個(gè)變量是由ThreadLocal維護(hù)的。
基本流程

ThreadLoalMap數(shù)據(jù)結(jié)構(gòu)
ThreadLoalMap是ThreadLocal中的一個(gè)靜態(tài)內(nèi)部類(lèi),類(lèi)似HashMap的數(shù)據(jù)結(jié)構(gòu),但并沒(méi)有實(shí)現(xiàn)Map接口。
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
private static final int INITIAL_CAPACITY = 16;
// ...
}
ThreadLoalMap中初始化了一個(gè)大小16的Entry數(shù)組,Entry對(duì)象用來(lái)保存每一個(gè)key-value鍵值對(duì)。通過(guò)上面的set方法,我們已經(jīng)知道其中的key永遠(yuǎn)都是ThreadLocal對(duì)象。

Hash沖突及解決
Entry在table中存儲(chǔ)位置是通過(guò)hashcode算法獲得。
在向ThreadLocalMap中的Entry數(shù)值存儲(chǔ)Entry對(duì)象時(shí),會(huì)根據(jù)ThreadLocal對(duì)象的hash值,定位到table中的位置i。分三種情況:
- 如果當(dāng)前位置為空的,直接將Entry存放在對(duì)應(yīng)位置;
- 如果位置i已經(jīng)有值且這個(gè)Entry對(duì)象的key正好是即將設(shè)置的key,那么重新設(shè)置Entry中的value;
- 如果位置i的Entry對(duì)象和即將設(shè)置的key沒(méi)關(guān)系,則尋找一個(gè)空位置;
計(jì)算hash值便會(huì)有hash沖突出現(xiàn),常見(jiàn)的解決方法有:再哈希法、開(kāi)放地址法、建立公共溢出區(qū)、鏈?zhǔn)降刂贩ǖ取?/p>
上面的流程可以看出這里采用的是開(kāi)放地址方法,如果當(dāng)前位置有值,就繼續(xù)尋找下一個(gè)位置,注意table[len-1]的下一個(gè)位置是table[0],就像是一個(gè)環(huán)形數(shù)組,所以也叫閉散列法。 如果一直都找不到空位置就會(huì)出現(xiàn)死循環(huán),發(fā)生內(nèi)存溢出。當(dāng)然有擴(kuò)容機(jī)制,一般不會(huì)找不到空位置的。
ThreadLocal內(nèi)存泄露
內(nèi)存引用鏈路
根據(jù)前面對(duì)ThreadLocal的分析,得知每個(gè)Thread維護(hù)一個(gè)ThreadLocalMap,它key是ThreadLocal實(shí)例本身,value是業(yè)務(wù)需要存儲(chǔ)的Object。也就是說(shuō)ThreadLocal本身并不存儲(chǔ)值,它只是作為一個(gè)key來(lái)讓線程從ThreadLocalMap獲取value。
ThreadLocalMap是使用ThreadLocal的弱引用作為Key的,弱引用的對(duì)象在GC時(shí)會(huì)被回收。因此使用了ThreadLocal后,引用鏈如圖所示:(其中虛線表示弱引用。)

引用類(lèi)型
強(qiáng)引用:java默認(rèn)的引用類(lèi)型,例如 Object a = new Object();其中 a 為強(qiáng)引用,new Object()為一個(gè)具體的對(duì)象。一個(gè)對(duì)象從根路徑能找到強(qiáng)引用指向它,jvm虛擬機(jī)就不會(huì)回收。
軟引用(SoftReference):進(jìn)行年輕代的垃圾回收不會(huì)觸發(fā)SoftReference所指向?qū)ο蟮幕厥眨?strong>但如果觸發(fā)Full GC,那SoftReference所指向的對(duì)象將被回收。備注:是除了軟引用之外沒(méi)有其他強(qiáng)引用引用的情況下。
弱引用(WeakReference) :如果對(duì)象除了有弱引用指向它后沒(méi)有其他強(qiáng)引用關(guān)聯(lián)它,當(dāng)進(jìn)行年輕代垃圾回收時(shí),該引用指向的對(duì)象就會(huì)被垃圾回收器回收。
虛引用(PhantomeReference) :該引用指向的對(duì)象,無(wú)法對(duì)垃圾收集器收集對(duì)象時(shí)產(chǎn)生任何影響,但在執(zhí)行垃圾回收后垃圾收集器會(huì)通過(guò)注冊(cè)在PhantomeReference上的隊(duì)列來(lái)通知應(yīng)用程序?qū)ο蟊换厥铡?/strong>
為什么使用弱引用而不是強(qiáng)引用?
問(wèn)題1:從表面上看內(nèi)存泄漏的根源在于使用了弱引用,但為什么JDK采用了弱引用的實(shí)現(xiàn)而不是強(qiáng)引用呢?
答案是:弱引用反而是為了解決內(nèi)存存儲(chǔ)問(wèn)題而專(zhuān)門(mén)使用的。
問(wèn)題2:如果應(yīng)用程序覺(jué)得ThreadLocal對(duì)象的使命完成,將threadLocal ref 設(shè)置為null,如果Entry中引用ThreadLocald對(duì)象的引用類(lèi)型設(shè)置為強(qiáng)引用的話(huà),會(huì)發(fā)生什么問(wèn)題?
答案是:ThreadLocal對(duì)象會(huì)無(wú)法被垃圾回收器回收,因?yàn)閺膖hread對(duì)象出發(fā),有強(qiáng)引用指向ThreadLocal的object。此時(shí)會(huì)違背用戶(hù)的初衷,造成所謂的內(nèi)存泄露。
我們先來(lái)假設(shè)一下,如果key使用強(qiáng)引用,那么在其他持有ThreadLocal引用的對(duì)象都回收了,但ThreadLocalMap依舊持有ThreadLocal的強(qiáng)引用,這就導(dǎo)致ThreadLocal不會(huì)被回收,從而導(dǎo)致Entry內(nèi)存泄露。
對(duì)照一下,弱引用的情況。持有ThreadLocal引用的對(duì)象都回收了,ThreadLocalMap持有的是ThreadLocal的弱引用,會(huì)被自動(dòng)回收。只不過(guò)對(duì)應(yīng)的value值,需要在下次調(diào)用set/get/remove方法時(shí)會(huì)被清除。
泄露原因分析
當(dāng)Thread執(zhí)行完會(huì)被銷(xiāo)毀,Thread.threadLocals指向的ThreadLocalMap實(shí)例也隨之變?yōu)槔?,它里面存放的Entity也會(huì)被回收。這種情況是不會(huì)發(fā)生內(nèi)存泄漏的。
發(fā)生內(nèi)存泄露的場(chǎng)景一般存在于線程池的情況下。 此時(shí),Thread生命周期比較長(zhǎng)(存在循環(huán)使用),threadLocals引用一直存在,當(dāng)其存放的ThreadLocal被回收(弱引用生命周期比較短)后,對(duì)應(yīng)的Entity就成了key為null的實(shí)例,但value值不會(huì)被回收。 如果此Entity一直不被get()、set()、remove(),就一直不會(huì)被回收,也就發(fā)生了內(nèi)存泄漏。
所以,通常在使用完ThreadLocal后需要調(diào)用remove()方法進(jìn)行內(nèi)存的清除。
接下來(lái)我們?cè)傺由煲幌?,想再?lái)談?wù)劸W(wǎng)絡(luò)上關(guān)于ThreadLocalMap中存儲(chǔ)大量Entry對(duì)象導(dǎo)致的內(nèi)存“泄露”問(wèn)題?
網(wǎng)絡(luò)觀點(diǎn):在使用ThreadLocal中set方法與remove方法需要成對(duì)執(zhí)行,需要沒(méi)有執(zhí)行remove方法會(huì)造成內(nèi)存泄露?甚至造成內(nèi)存溢出?
我的觀點(diǎn):當(dāng)然能成對(duì)使用當(dāng)然更好,但在實(shí)際情況中,其實(shí)不調(diào)用remove方法也不太容易造成內(nèi)存溢出,因?yàn)閺拇鎯?chǔ)結(jié)構(gòu)來(lái)看,除非創(chuàng)建海量線程,并且這些線程都不釋放,導(dǎo)致大量線程內(nèi)部持有的ThreadLocalMap中對(duì)象一直不會(huì)釋放,但一個(gè)線程所持有的Entry對(duì)象個(gè)數(shù)不多,取決于關(guān)聯(lián)的ThreadLocal對(duì)象個(gè)數(shù),故我們需要的關(guān)注點(diǎn)而不是remove方法,而是防止線程資源泄露。
ThreadLocal應(yīng)用場(chǎng)景
- 線程間數(shù)據(jù)隔離,各線程的ThreadLocal互不影響;
- 方便同一個(gè)線程使用某一對(duì)象,避免不必要的參數(shù)傳遞;
- 全鏈路追蹤中的traceId或者流程引擎中上下文的傳遞一般采用ThreadLocal;
- Spring事務(wù)管理器采用了ThreadLocal;
- Spring MVC的RequestContextHolder的實(shí)現(xiàn)使用了ThreadLocal;
到此這篇關(guān)于ThreadLocal的基本原理的文章就介紹到這了,更多相關(guān)ThreadLocal原理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 詳解Java中的ThreadLocal
- java線程本地變量ThreadLocal詳解
- 深入解析Java中ThreadLocal線程類(lèi)的作用和用法
- java ThreadLocal使用案例詳解
- Java ThreadLocal用法實(shí)例詳解
- 淺談Java中ThreadLocal內(nèi)存泄露的原因及處理方式
- java中ThreadLocal取不到值的兩種原因
- 實(shí)例詳解Java中ThreadLocal內(nèi)存泄露
- Java ThreadLocal原理解析以及應(yīng)用場(chǎng)景分析案例詳解
- Java中ThreadLocal變量存儲(chǔ)類(lèi)的原理,使用場(chǎng)景及內(nèi)存泄漏問(wèn)題
相關(guān)文章
java數(shù)據(jù)類(lèi)型與變量的安全性介紹
這篇文章主要介紹了java數(shù)據(jù)類(lèi)型與變量的安全性介紹,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的朋友可以參考一下2022-07-07
基于SpringBoot實(shí)現(xiàn)大文件分塊上傳功能
這篇文章主要介紹了基于SpringBoot實(shí)現(xiàn)大文件分塊上傳功能,實(shí)現(xiàn)原理其實(shí)很簡(jiǎn)單,核心就是客戶(hù)端把大文件按照一定規(guī)則進(jìn)行拆分,比如20MB為一個(gè)小塊,分解成一個(gè)一個(gè)的文件塊,然后把這些文件塊單獨(dú)上傳到服務(wù)端,需要的朋友可以參考下2024-09-09
關(guān)于application.yml基礎(chǔ)配置以及讀取方式
這篇文章主要介紹了關(guān)于application.yml基礎(chǔ)配置以及讀取方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-07-07
阿里云部署SpringBoot項(xiàng)目啟動(dòng)后被殺進(jìn)程的問(wèn)題解析
這篇文章主要介紹了阿里云部署SpringBoot項(xiàng)目啟動(dòng)后被殺進(jìn)程的問(wèn)題,本文給大家分享問(wèn)題原因所在及解決步驟,需要的朋友可以參考下2023-09-09
springboot接收J(rèn)SON實(shí)現(xiàn)示例解析
這篇文章主要為大家介紹了springboot如何接收J(rèn)SON的實(shí)現(xiàn)示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07
SpringBoot如何讀取配置文件中的數(shù)據(jù)到map和list
這篇文章主要介紹了SpringBoot如何讀取配置文件中的數(shù)據(jù)到map和list,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02

