ThreadLocal是Java中的一種線程綁定機制,可以爲每一個使用該變量的線程都提供一個變量值的副本,並且每一個線程都可以獨立地改變自己的副本,而不會與其它線程的副本發生衝突,解決了變量併發訪問的衝突問題。
每個線程內部有一個 ThreadLocal.ThreadLocalMap 類型的成員變量 threadLocals,這個 threadLocals 存儲了與該線程相關的所有ThreadLocal 變量及其對應的值,也就是說,ThreadLocal 變量及其對應的值就是該Map中的一個 Entry,更直白地,threadLocals中每個Entry的Key是ThreadLocal 變量本身,而Value是該ThreadLocal變量對應的值。
ThreadLocal可能引起的內存泄露
下面是ThreadLocalMap的部分源碼,我們可以看出ThreadLocalMap裏面對Key的引用是弱引用。那麼,就存在這樣的情況:當釋放掉對threadlocal對象的強引用後,map裏面的value沒有被回收,但卻永遠不會被訪問到了,因此ThreadLocal存在着內存泄露問題。
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}
...
}
看下面的圖示, 實線代表強引用,虛線代表弱引用。每個thread中都存在一個map,map的類型是上文提到的ThreadLocal.ThreadLocalMap,該map中的key爲一個ThreadLocal實例。這個Map的確使用了弱引用,不過弱引用只是針對key,每個key都弱引用指向ThreadLocal對象。一旦把threadlocal實例置爲null以後,那麼將沒有任何強引用指向ThreadLocal對象,因此ThreadLocal對象將會被 Java GC 回收。但是,與之關聯的value卻不能回收,因爲存在一條從current thread連接過來的強引用。 只有當前thread結束以後, current thread就不會存在棧中,強引用斷開,Current Thread、Map及value將全部被Java GC回收。
所以,得出一個結論就是:只要這個線程對象被Java GC回收,就不會出現內存泄露。但是如果只把ThreadLocal引用指向null而線程對象依然存在,那麼此時Value是不會被回收的,這就發生了我們認爲的內存泄露。比如,在使用線程池的時候,線程結束是不會銷燬的而是會再次使用的,這種情形下就可能出現ThreadLocal內存泄露。
Java爲了最小化減少內存泄露的可能性和影響,在ThreadLocal進行get、set操作時會清除線程Map裏所有key爲null的value。所以最怕的情況就是,ThreadLocal對象設null了,開始發生“內存泄露”,然後使用線程池,線程結束後被放回線程池中而不銷燬,那麼如果這個線程一直不被使用或者分配使用了又不再調用get/set方法,那麼這個期間就會發生真正的內存泄露。因此,最好的做法是:在不使用該ThreadLocal對象時,及時調用該對象的remove方法去移除ThreadLocal.ThreadLocalMap中的對應Entry。