ThreadLocal是什么
ThreadLocal是一個(gè)本地線(xiàn)程副本變量工具類(lèi)。主要用于將私有線(xiàn)程和該線(xiàn)程存放的副本對(duì)象做一個(gè)映射,各個(gè)線(xiàn)程之間的變量互不干擾,在高并發(fā)場(chǎng)景下,可以實(shí)現(xiàn)無(wú)狀態(tài)的調(diào)用,特別適用于各個(gè)線(xiàn)程依賴(lài)不通的變量值完成操作的場(chǎng)景。
下圖為T(mén)hreadLocal的內(nèi)部結(jié)構(gòu)圖

從上面的結(jié)構(gòu)圖,我們已經(jīng)窺見(jiàn)ThreadLocal的核心機(jī)制:
每個(gè)Thread線(xiàn)程內(nèi)部都有一個(gè)Map。
Map里面存儲(chǔ)線(xiàn)程本地對(duì)象(key)和線(xiàn)程的變量副本(value)
但是,Thread內(nèi)部的Map是由ThreadLocal維護(hù)的,由ThreadLocal負(fù)責(zé)向map獲取和設(shè)置線(xiàn)程的變量值。
所以對(duì)于不同的線(xiàn)程,每次獲取副本值時(shí),別的線(xiàn)程并不能獲取到當(dāng)前線(xiàn)程的副本值,形成了副本的隔離,互不干擾。
ThreadLocalMap
ThreadLocalMap是ThreadLocal的內(nèi)部類(lèi),沒(méi)有實(shí)現(xiàn)Map接口,用獨(dú)立的方式實(shí)現(xiàn)了Map的功能,其內(nèi)部的Entry也獨(dú)立實(shí)現(xiàn)。
和HashMap的最大的不同在于,ThreadLocalMap結(jié)構(gòu)非常簡(jiǎn)單,沒(méi)有next引用,也就是說(shuō)ThreadLocalMap中解決Hash沖突的方式并非鏈表的方式,而是采用線(xiàn)性探測(cè)的方式。(ThreadLocalMap如何解決沖突?)
在ThreadLocalMap中,也是用Entry來(lái)保存K-V結(jié)構(gòu)數(shù)據(jù)的。但是Entry中key只能是ThreadLocal對(duì)象,這點(diǎn)被Entry的構(gòu)造方法已經(jīng)限定死了。
static class Entry extends WeakReference<ThreadLocal> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}
注意了!!
Entry繼承自WeakReference(弱引用,生命周期只能存活到下次GC前),但只有Key是弱引用類(lèi)型的,Value并非弱引用。(問(wèn)題馬上就來(lái)了)
由于ThreadLocalMap的key是弱引用,而Value是強(qiáng)引用。這就導(dǎo)致了一個(gè)問(wèn)題,ThreadLocal在沒(méi)有外部對(duì)象強(qiáng)引用時(shí),發(fā)生GC時(shí)弱引用Key會(huì)被回收,而Value不會(huì)回收。
當(dāng)線(xiàn)程沒(méi)有結(jié)束,但是ThreadLocal已經(jīng)被回收,則可能導(dǎo)致線(xiàn)程中存在ThreadLocalMap<null, Object>的鍵值對(duì),造成內(nèi)存泄露。(ThreadLocal被回收,ThreadLocal關(guān)聯(lián)的線(xiàn)程共享變量還存在)。
如何避免泄漏
為了防止此類(lèi)情況的出現(xiàn),我們有兩種手段。
1、使用完線(xiàn)程共享變量后,顯示調(diào)用ThreadLocalMap.remove方法清除線(xiàn)程共享變量;
既然Key是弱引用,那么我們要做的事,就是在調(diào)用ThreadLocal的get()、set()方法時(shí)完成后再調(diào)用remove方法,將Entry節(jié)點(diǎn)和Map的引用關(guān)系移除,這樣整個(gè)Entry對(duì)象在GC Roots分析后就變成不可達(dá)了,下次GC的時(shí)候就可以被回收。
2、JDK建議ThreadLocal定義為private static,這樣ThreadLocal的弱引用問(wèn)題則不存在了。