1. threadlocal
1.1 解釋
ThreadLocal是線程變量孤页,即ThreadLocal中填充的變量屬于當(dāng)前線程,該變量對其他線程而言是隔離的尿赚。ThreadLocal為變量在每個(gè)線程中都創(chuàng)建了一個(gè)副本散庶,那么每個(gè)線程可以訪問自己內(nèi)部的副本變量。
1.2 作用
- 1凌净、在進(jìn)行對象跨層傳遞的時(shí)候悲龟,使用ThreadLocal可以避免多次傳遞,打破層次間的約束冰寻。
- 2须教、線程間數(shù)據(jù)隔離
- 3、進(jìn)行事務(wù)操作斩芭,用于存儲(chǔ)線程事務(wù)信息轻腺。
- 4、數(shù)據(jù)庫連接划乖,Session會(huì)話管理贬养。
1.3 結(jié)構(gòu)
ThreadLocal結(jié)構(gòu):
說明:每個(gè)thread持有一個(gè)ThreadLocalMap,里面存儲(chǔ)了線程中定義的一系列ThreadLocal琴庵,需要取某個(gè)threadlocal值時(shí)误算,首先根據(jù)currentThread取到threadlocalmap,然后以該threadlocal為key拿到threadlocal的值迷殿。
1.4 內(nèi)存泄漏
ThreadLocal本身并不真正存儲(chǔ)線程的變量值儿礼,它只是一個(gè)工具,用來維護(hù)Thread內(nèi)部的Map庆寺,幫助存和取蚊夫。注意ThreadLocal結(jié)構(gòu)圖的虛線,它代表一個(gè)弱引用類型懦尝,而弱引用的生命周期只能存活到下次GC前知纷。
ThreadLocal在ThreadLocalMap中是以一個(gè)弱引用身份被Entry中的Key引用的,因此如果ThreadLocal沒有外部強(qiáng)引用來引用它导披,那么ThreadLocal會(huì)在下次JVM垃圾收集時(shí)被回收屈扎。這個(gè)時(shí)候就會(huì)出現(xiàn)Entry中Key已經(jīng)被回收,出現(xiàn)一個(gè)null Key的情況撩匕,外部讀取ThreadLocalMap中的元素是無法通過null Key來找到Value的鹰晨。因此如果當(dāng)前線程的生命周期很長,一直存在,那么其內(nèi)部的ThreadLocalMap對象也一直生存下來模蜡,這些null key就存在一條強(qiáng)引用鏈的關(guān)系一直存在:Thread --> ThreadLocalMap-->Entry-->Value漠趁,這條強(qiáng)引用鏈會(huì)導(dǎo)致Entry不會(huì)回收,Value也不會(huì)回收忍疾,但Entry中的Key卻已經(jīng)被回收的情況闯传,造成內(nèi)存泄漏。
但是JVM做了一些措施來保證ThreadLocal盡量不會(huì)內(nèi)存泄漏:在ThreadLocal的get()卤妒、set()甥绿、remove()方法調(diào)用的時(shí)候會(huì)清除掉線程ThreadLocalMap中所有Entry中Key為null的Value,并將整個(gè)Entry設(shè)置為null则披,利于下次內(nèi)存回收共缕。
但這樣也并不能保證ThreadLocal不會(huì)發(fā)生內(nèi)存泄漏,例如:
- 使用static的ThreadLocal士复,延長了ThreadLocal的生命周期图谷,可能導(dǎo)致的內(nèi)存泄漏。
- 分配使用了ThreadLocal又不再調(diào)用get()阱洪、set()便贵、remove()方法,那么就會(huì)導(dǎo)致內(nèi)存泄漏冗荸。
1.5 根源及解決
下面我們分兩種情況討論:
- key 使用強(qiáng)引用:引用的ThreadLocal的對象被回收了承璃,但是ThreadLocalMap還持有ThreadLocal的強(qiáng)引用,如果沒有手動(dòng)刪除蚌本,ThreadLocal不會(huì)被回收绸硕,導(dǎo)致Entry內(nèi)存泄漏。
- key 使用弱引用:引用的ThreadLocal的對象被回收了魂毁,由于ThreadLocalMap持有ThreadLocal的弱引用,即使沒有手動(dòng)刪除出嘹,ThreadLocal也會(huì)被回收席楚。value在下一次ThreadLocalMap調(diào)用set,get,remove的時(shí)候會(huì)被清除税稼。
比較兩種情況烦秩,我們可以發(fā)現(xiàn):由于ThreadLocalMap的生命周期跟Thread一樣長,如果都沒有手動(dòng)刪除對應(yīng)key郎仆,都會(huì)導(dǎo)致內(nèi)存泄漏只祠,但是使用弱引用可以多一層保障:弱引用ThreadLocal不會(huì)內(nèi)存泄漏,對應(yīng)的value在下一次ThreadLocalMap調(diào)用set,get,remove的時(shí)候會(huì)被清除扰肌。
因此抛寝,ThreadLocal內(nèi)存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一樣長,如果沒有手動(dòng)刪除對應(yīng)key的value就會(huì)導(dǎo)致內(nèi)存泄漏,而不是因?yàn)槿跻谩?/p>
解決:最簡單最有效的解決辦法即是顯式調(diào)用remove(ThreadLocal<?> key).