java中ThreadLocal的基本原理
源碼實(shí)現(xiàn)
一個(gè)線(xiàn)程內(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)讓線(xiàn)程從ThreadLocalMap獲取value。
ThreadLocalMap是使用ThreadLocal的弱引用作為Key的,弱引用的對(duì)象在GC時(shí)會(huì)被回收。因此使用了ThreadLocal后,引用鏈如圖所示:(其中虛線(xiàn)表示弱引用。)
引用類(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)景一般存在于線(xiàn)程池的情況下。 此時(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)建海量線(xiàn)程,并且這些線(xiàn)程都不釋放,導(dǎo)致大量線(xiàn)程內(nèi)部持有的ThreadLocalMap中對(duì)象一直不會(huì)釋放,但一個(gè)線(xiàn)程所持有的Entry對(duì)象個(gè)數(shù)不多,取決于關(guān)聯(lián)的ThreadLocal對(duì)象個(gè)數(shù),故我們需要的關(guān)注點(diǎn)而不是remove方法,而是防止線(xiàn)程資源泄露。
ThreadLocal應(yīng)用場(chǎng)景
- 線(xiàn)程間數(shù)據(jù)隔離,各線(xiàn)程的ThreadLocal互不影響;
- 方便同一個(gè)線(xiàn)程使用某一對(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?類(lèi)常用方法源碼解析
- TransmittableThreadLocal通過(guò)javaAgent實(shí)現(xiàn)線(xiàn)程傳遞并支持ForkJoin
- java開(kāi)發(fā)工作中對(duì)InheritableThreadLocal使用思考
- java編程ThreadLocal上下傳遞源碼解析
- Java ThreadLocal原理解析以及應(yīng)用場(chǎng)景分析案例詳解
- 面試官:java ThreadLocal真的會(huì)造成內(nèi)存泄露嗎
- java ThreadLocal線(xiàn)程局部變量常用方法使用場(chǎng)景示例詳解
相關(guān)文章
java8根據(jù)某一屬性過(guò)濾去重的實(shí)例
這篇文章主要介紹了java8根據(jù)某一屬性過(guò)濾去重的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-05-05用3個(gè)實(shí)例從原理到實(shí)戰(zhàn)講清楚Log4j史詩(shī)級(jí)漏洞
最近應(yīng)該很多人都在關(guān)注著一個(gè)漏洞Apache Log4j 2遠(yuǎn)程代碼執(zhí)行,該漏洞一旦被攻擊者利用會(huì)造成嚴(yán)重危害,這篇文章主要給大家介紹了關(guān)于如何用3個(gè)實(shí)例從原理到實(shí)戰(zhàn)講清楚Log4j史詩(shī)級(jí)漏洞的相關(guān)資料,需要的朋友可以參考下2021-12-12Java線(xiàn)程池execute()方法源碼全面解析
這篇文章主要介紹了Java線(xiàn)程池execute()方法源碼全面解析,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03Java 使用 HttpClient 發(fā)送 GET請(qǐng)求和 POST請(qǐng)求
本文主要介紹了Java 使用 HttpClient 發(fā)送 GET請(qǐng)求和 POST請(qǐng)求,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08Java鍵值對(duì)Pair的使用方式和操作實(shí)現(xiàn)
鍵值對(duì)是一種常見(jiàn)的數(shù)據(jù)結(jié)構(gòu),它由一個(gè)唯一的鍵和與之關(guān)聯(lián)的值組成,本文就來(lái)介紹一下Java鍵值對(duì)Pair的使用方式和操作實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下2023-12-12Java 數(shù)組元素倒序的三種方式(小結(jié))
這篇文章主要介紹了Java 數(shù)組元素倒序的三種方式(小結(jié)),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09