遇到了描述ThreadLocal的實現原理和內存泄漏的問題,以前看過ThreadLocal的實現原理,可是網上有不少文章將的很亂,其中有不少文章將ThreadLocal與線程同步機制混爲一談,特別注意的是ThreadLocal與線程同步無關,並非爲了解決多線程共享變量問題!
ThreadLocal官網解釋:數組
This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its {@code get} or {@code set} method) has its own, independently initialized copy of the variable. {@code ThreadLocal} instances are typically private static fields in classes that wish to associate state with a thread (e.g.,a user ID or Transaction ID)
->翻譯過來的大概意思就是:ThreadLocal類用來提供線程內部的局部變量。這些變量在多線程環境下訪問(經過get或set方法訪問)時能保證各個線程裏的變量相對獨立於其餘線程內的變量,ThreadLocal實例一般來講都是private static類型。
總結:ThreadLocal不是爲了解決多線程訪問共享變量,而是爲每一個線程建立一個單獨的變量副本,提供了保持對象的方法和避免參數傳遞的複雜性。服務器
ThreadLocal的主要應用場景爲按線程多實例(每一個線程對應一個實例)的對象的訪問,而且這個對象不少地方都要用到。例如:同一個網站登陸用戶,每一個用戶服務器會爲其開一個線程,每一個線程中建立一個ThreadLocal,裏面存用戶基本信息等,在不少頁面跳轉時,會顯示用戶信息或者獲得用戶的一些信息等頻繁操做,這樣多線程之間並無聯繫並且當前線程也能夠及時獲取想要的數據。多線程
ThreadLocal能夠看作是一個容器,容器裏面存放着屬於當前線程的變量。ThreadLocal類提供了四個對外開放的接口方法,這也是用戶操做ThreadLocal類的基本方法:
(1) void set(Object value)設置當前線程的線程局部變量的值。
(2) public Object get()該方法返回當前線程所對應的線程局部變量。
(3) public void remove()將當前線程局部變量的值刪除,目的是爲了減小內存的佔用,該方法是JDK 5.0新增的方法。須要指出的是,當線程結束後,對應該線程的局部變量將自動被垃圾回收,因此顯式調用該方法清除線程的局部變量並非必須的操做,但它能夠加快內存回收的速度。
(4) protected Object initialValue()返回該線程局部變量的初始值,該方法是一個protected的方法,顯然是爲了讓子類覆蓋而設計的。這個方法是一個延遲調用方法,在線程第1次調用get()或set(Object)時才執行,而且僅執行1次,ThreadLocal中的缺省實現直接返回一個null。ide
能夠經過上述的幾個方法實現ThreadLocal中變量的訪問,數據設置,初始化以及刪除局部變量,那ThreadLocal內部是如何爲每個線程維護變量副本的呢?網站
其實在ThreadLocal類中有一個靜態內部類ThreadLocalMap(其相似於Map),用鍵值對的形式存儲每個線程的變量副本,ThreadLocalMap中元素的key爲當前ThreadLocal對象,而value對應線程的變量副本,每一個線程可能存在多個ThreadLocal。this
源代碼:spa
1 /** 2 Returns the value in the current thread's copy of this 3 thread-local variable. If the variable has no value for thecurrent thread, it is first initialized to the value returned by an invocation of the {@link #initialValue} method. 4 @return the current thread's value of this thread-local 5 */ 6 public T get() { 7 Thread t = Thread.currentThread();//當前線程 8 ThreadLocalMap map = getMap(t);//獲取當前線程對應的ThreadLocalMap 9 if (map != null) { 10 ThreadLocalMap.Entry e = map.getEntry(this);//獲取對應ThreadLocal的變量值 11 if (e != null) { 12 @SuppressWarnings("unchecked") 13 T result = (T)e.value; 14 return result; 15 } 16 } 17 return setInitialValue();//若當前線程還未建立ThreadLocalMap,則返回調用此方法並在其中調用createMap方法進行建立並返回初始值。 18 } 19 //設置變量的值 20 public void set(T value) { 21 Thread t = Thread.currentThread(); 22 ThreadLocalMap map = getMap(t); 23 if (map != null) 24 map.set(this, value); 25 else 26 createMap(t, value); 27 } 28 private T setInitialValue() { 29 T value = initialValue(); 30 Thread t = Thread.currentThread(); 31 ThreadLocalMap map = getMap(t); 32 if (map != null) 33 map.set(this, value); 34 else 35 createMap(t, value); 36 return value; 37 } 38 /** 39 爲當前線程建立一個ThreadLocalMap的threadlocals,並將第一個值存入到當前map中 40 @param t the current thread 41 @param firstValue value for the initial entry of the map 42 */ 43 void createMap(Thread t, T firstValue) { 44 t.threadLocals = new ThreadLocalMap(this, firstValue); 45 } 46 //刪除當前線程中ThreadLocalMap對應的ThreadLocal 47 public void remove() { 48 ThreadLocalMap m = getMap(Thread.currentThread()); 49 if (m != null) 50 m.remove(this); 51 }
上述是在ThreadLocal類中的幾個主要的方法,他們的核心都是對其內部類ThreadLocalMap進行操做,下面看一下該類的源代碼:線程
1 static class ThreadLocalMap { 2 //map中的每一個節點Entry,其鍵key是ThreadLocal而且仍是弱引用,這也致使了後續會產生內存泄漏問題的緣由。 3 static class Entry extends WeakReference<ThreadLocal<?>> { 4 Object value; 5 Entry(ThreadLocal<?> k, Object v) { 6 super(k); 7 value = v; 8 } 9 /** 10 * 初始化容量爲16,覺得對其擴充也必須是2的指數 11 */ 12 private static final int INITIAL_CAPACITY = 16; 13 /** 14 * 真正用於存儲線程的每一個ThreadLocal的數組,將ThreadLocal和其對應的值包裝爲一個Entry。 15 */ 16 private Entry[] table; 17 18 19 ///....其餘的方法和操做都和map的相似 20 }
總之,爲不一樣線程建立不一樣的ThreadLocalMap,用線程自己爲區分點,每一個線程之間其實沒有任何的聯繫,說是說存放了變量的副本,其實能夠理解爲爲每一個線程單獨new了一個對象。翻譯
在上面提到過,每一個thread中都存在一個map, map的類型是ThreadLocal.ThreadLocalMap. Map中的key爲一個threadlocal實例. 這個Map的確使用了弱引用,不過弱引用只是針對key. 每一個key都弱引用指向threadlocal. 當把threadlocal實例置爲null之後,沒有任何強引用指向threadlocal實例,因此threadlocal將會被gc回收. 可是,咱們的value卻不能回收,由於存在一條從current thread鏈接過來的強引用. 只有當前thread結束之後, current thread就不會存在棧中,強引用斷開, Current Thread, Map, value將所有被GC回收.
因此得出一個結論就是隻要這個線程對象被gc回收,就不會出現內存泄露,但在threadLocal設爲null和線程結束這段時間不會被回收的,就發生了咱們認爲的內存泄露。其實這是一個對概念理解的不一致,也沒什麼好爭論的。最要命的是線程對象不被回收的狀況,這就發生了真正意義上的內存泄露。好比使用線程池的時候,線程結束是不會銷燬的,會再次使用的。就可能出現內存泄露。設計