如何去理解ThreadLocal?
ThreadLocal是什么?
ThreadLocal是一個本地線程副本變量工具類。主要用于將私有線程和該線程存放的副本對象做一個映射,各個線程之間的變量互不干擾,在高并發(fā)場景下,可以實現(xiàn)無狀態(tài)的調(diào)用,特別適用于各個線程依賴不通的變量值完成操作的場景.
ThreadLocal特點:就是在一個線程里放一個數(shù)據(jù),不管中間執(zhí)行了什么操作。只要想獲取出來的時候,調(diào)用get就可以得到保存進去的數(shù)據(jù).
ThreadLocal內(nèi)部結(jié)構圖從上面的結(jié)構圖中,我們可以看到ThreadLocal的核心機制
每個Thread 內(nèi)部都有一個Map。Map里面存儲線程本地對象(key) 和線程的變量副本(value)。Thread 內(nèi)部的Map是由 ThreadLocal為的,由ThreadLocal負責向map獲取和設置線程的變量值。Thread線程內(nèi)部的Map在類中描述如下:
ThreadLocal 為什么會內(nèi)存泄漏我們先分析一下ThreadLocalMap
我們可以知道每個Thread 維護一個 ThreadLocalMap,這個映射表的 key 是 ThreadLocal實例本身,value 是真正需要存儲的 Object,也就是說 ThreadLocal 本身并不存儲值,它只是作為一個 key 來讓線程從 ThreadLocalMap 獲取 value。仔細觀察ThreadLocalMap,這個map是使用 ThreadLocal 的弱引用作為 Key 的,弱引用的對象在 GC 時會被回收。
這樣,當把threadlocal變量置為null以后,沒有任何強引用指向threadlocal實例,所以threadlocal將會被gc回收。這樣一來,ThreadLocalMap中就會出現(xiàn)key為null的Entry,就沒有辦法訪問這些key為null的Entry的value,如果當前線程再遲遲不結(jié)束的話,這些key為null的Entry的value就會一直存在一條強引用鏈:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value,而這塊value永遠不會被訪問到了,所以存在著內(nèi)存泄露。
其實java 開發(fā)者,也考慮到了此問題,所以在get(),set()的時候,調(diào)用了expungeStaleEntry方法用來清除Entry中Key為null的Value,但是這是不及時的,也不是每次都會執(zhí)行的,所以一些情況下還是會發(fā)生內(nèi)存泄露。只有remove()方法中顯式調(diào)用了expungeStaleEntry方法。
下面看下ThreadLocal 的get()方法的實現(xiàn):
下面繼續(xù)看 map.getEntry方法
當key 為null 時,調(diào)用getEntryAfterMiss方法
當key 為null 時,調(diào)用expungeStaleEntry 方法
也許有人會好奇,有上面方法,為什么還會導致內(nèi)存泄漏呢?
一般我們設置的ThreadLocal設置為static的,static 變量可以作為GCRoot的根節(jié)點,所以會一直存在初始化了ThreadLocal, 調(diào)用set ,get 而沒有調(diào)用remove方法,所以會導致內(nèi)存泄漏。比如get方法,只有ThreadLocalMap中沒有所需要的key時,才會調(diào)用清除方法