面試:說說你對 ThreadLocal 的認識?
點擊上方藍色“程序猿DD”,選擇“設為星標”
回復“資源”獲取獨家整理的學習資料!

ThreadLocal的原理和實現(xiàn)
ThreadLoal 變量,線程局部變量,同一個 ThreadLocal 所包含的對象,在不同的 Thread 中有不同的副本。ThreadLocal 變量通常被private static修飾。當一個線程結(jié)束時,它所使用的所有 ThreadLocal 相對的實例副本都可被回收。
一個線程內(nèi)可以存在多個 ThreadLocal 對象,所以其實是 ThreadLocal 內(nèi)部維護了一個 Map ,這個 Map 不是直接使用的 HashMap ,而是 ThreadLocal 實現(xiàn)的一個叫做 ThreadLocalMap 的靜態(tài)內(nèi)部類。
而我們使用的 get()、set() 方法其實都是調(diào)用了這個ThreadLocalMap類對應的 get()、set() 方法。這個儲值的Map并非ThreadLocal的成員變量,而是java.lang.Thread 類的成員變量。ThreadLocalMap實例是作為java.lang.Thread的成員變量存儲的,每個線程有唯一的一個threadLocalMap。
這個map以ThreadLocal對象為key,”線程局部變量”為值,所以一個線程下可以保存多個”線程局部變量”。對ThreadLocal的操作,實際委托給當前Thread,每個Thread都會有自己獨立的ThreadLocalMap實例,存儲的倉庫是Entry[] table;Entry的key為ThreadLocal,value為存儲內(nèi)容;因此在并發(fā)環(huán)境下,對ThreadLocal的set或get,不會有任何問題。
由于Tomcat線程池的原因,我最初使用的”線程局部變量”保存的值,在下一次請求依然存在(同一個線程處理),這樣每次請求都是在本線程中取值。所以在線程池的情況下,處理完成后主動調(diào)用該業(yè)務treadLocal的remove()方法,將”線程局部變量”清空,避免本線程下次處理的時候依然存在舊數(shù)據(jù)。
ThreadLocal為什么要使用弱引用和內(nèi)存泄露問題
在ThreadLocal中內(nèi)存泄漏是指ThreadLocalMap中的Entry中的key為null,而value不為null。因為key為null導致value一直訪問不到,而根據(jù)可達性分析導致在垃圾回收的時候進行可達性分析的時候,value可達從而不會被回收掉,但是該value永遠不能被訪問到,這樣就存在了內(nèi)存泄漏。
如果 key 是強引用,那么發(fā)生 GC 時 ThreadLocalMap 還持有 ThreadLocal 的強引用,會導致 ThreadLocal 不會被回收,從而導致內(nèi)存泄漏。弱引用 ThreadLocal 不會內(nèi)存泄漏,對應的 value 在下一次 ThreadLocalMap 調(diào)用 set、get、remove 方法時被清除,這算是最優(yōu)的解決方案。
Map中的key為一個threadlocal實例.如果使用強引用,當ThreadLocal對象(假設為ThreadLocal@123456)的引用被回收了,ThreadLocalMap本身依然還持有ThreadLocal@123456的強引用,如果沒有手動刪除這個key,則ThreadLocal@123456不會被回收,所以只要當前線程不消亡,ThreadLocalMap引用的那些對象就不會被回收,可以認為這導致Entry內(nèi)存泄漏。
如果使用弱引用,那指向ThreadLocal@123456對象的引用就兩個:ThreadLocal強引用和ThreadLocalMap中Entry的弱引用。一旦ThreadLocal強引用被回收,則指向ThreadLocal@123456的就只有弱引用了,在下次gc的時候,這個ThreadLocal@123456就會被回收。
雖然上述的弱引用解決了key,也就是線程的ThreadLocal能及時被回收,但是value卻依然存在內(nèi)存泄漏的問題。當把threadlocal實例置為null以后,沒有任何強引用指向threadlocal實例,所以threadlocal將會被gc回收.map里面的value卻沒有被回收.而這塊value永遠不會被訪問到了.
所以存在著內(nèi)存泄露,因為存在一條從current thread連接過來的強引用.只有當前thread結(jié)束以后, current thread就不會存在棧中,強引用斷開, Current Thread, Map, value將全部被GC回收.所以當線程的某個localThread使用完了,馬上調(diào)用threadlocal的remove方法,就不會發(fā)生這種情況了。
另外其實只要這個線程對象及時被gc回收,這個內(nèi)存泄露問題影響不大,但在threadLocal設為null到線程結(jié)束中間這段時間不會被回收的,就發(fā)生了我們認為的內(nèi)存泄露。最要命的是線程對象不被回收的情況,這就發(fā)生了真正意義上的內(nèi)存泄露。比如使用線程池的時候,線程結(jié)束是不會銷毀的,會再次使用,就可能出現(xiàn)內(nèi)存泄露。
往期推薦
