ThreadLocal的理解

遇到了描述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)
  • 1

->翻譯過來的大概意思就是:ThreadLocal類用來提供線程內部的局部變量。這些變量在多線程環境下訪問(經過get或set方法訪問)時能保證各個線程裏的變量相對獨立於其餘線程內的變量,ThreadLocal實例一般來講都是private static類型。 
總結:ThreadLocal不是爲了解決多線程訪問共享變量,而是爲每一個線程建立一個單獨的變量副本,提供了保持對象的方法和避免參數傳遞的複雜性。服務器

ThreadLocal的主要應用場景爲按線程多實例(每一個線程對應一個實例)的對象的訪問,而且這個對象不少地方都要用到。例如:同一個網站登陸用戶,每一個用戶服務器會爲其開一個線程,每一個線程中建立一個ThreadLocal,裏面存用戶基本信息等,在不少頁面跳轉時,會顯示用戶信息或者獲得用戶的一些信息等頻繁操做,這樣多線程之間並無聯繫並且當前線程也能夠及時獲取想要的數據。多線程

2、實現原理

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了一個對象。翻譯

3、內存泄漏問題

      在上面提到過,每一個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和線程結束這段時間不會被回收的,就發生了咱們認爲的內存泄露。其實這是一個對概念理解的不一致,也沒什麼好爭論的。最要命的是線程對象不被回收的狀況,這就發生了真正意義上的內存泄露。好比使用線程池的時候,線程結束是不會銷燬的,會再次使用的。就可能出現內存泄露。設計

相關文章
相關標籤/搜索