平時常說的ThreadLocal,今天就完全解決它

  • 前言
  • 1、瞭解ThreadLocal的做用
  • 2、ThreadLocal簡單使用
  • 3、ThreadLocal原理
    • 3.1 ThreadLocal的存取過程
    • 3.2 探究ThreadLocalMap對象
    • 3.3 ThreadLocal對象的回收
  • 4、ThreadLocal應用場景

前言

ThreadLocal是多線程處理中很是重要的一個工具,好比數據庫鏈接池存放Connection、存放本地參數等做用,面試也常常會問到它的應用及原理,本文就將從外到內地學習一下ThreadLocal。javascript

1、瞭解ThreadLocal的做用

ThreadLocal顧名思義是線程私有的局部變量存儲容器,能夠理解成每一個線程都有本身專屬的存儲容器,它用來存儲線程私有變量,其實它只是一個外殼,內部真正存取是一個Map,後面會仔細講解。每一個線程能夠經過set()get()存取變量,多線程間沒法訪問各自的局部變量,至關於在每一個線程間創建了一個隔板。只要線程處於活動狀態,它所對應的ThreadLocal實例就是可訪問的,線程被終止後,它的全部實例將被垃圾收集。總之記住一句話:ThreadLocal存儲的變量屬於當前線程java

2、ThreadLocal簡單使用

話很少說先看一下ThreadLocal的一個簡單案例面試

Code:數據庫

public class Test implements Runnable { private static AtomicInteger counter = new AtomicInteger(100); private static ThreadLocal<String> threadInfo = new ThreadLocal<String>() { @Override protected String initialValue() { return "[" + Thread.currentThread().getName() + "," + counter.getAndIncrement() + "]"; } }; @Override public void run() { System.out.println("threadInfo value:" + threadInfo.get()); } public static void main(String[] args) throws InterruptedException { Thread thread1 = new Thread(new Test()); Thread thread2 = new Thread(new Test()); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println("threadInfo value in main:" + threadInfo.get()); } } 

Output:markdown

threadInfo value:[Thread-0,100] threadInfo value:[Thread-1,101] threadInfo value in main:[main,102] 

上述代碼中我用ThreadLocal來存儲線程的信息,其格式爲[線程名,線程ID],定義的變量是靜態的。從運行結果能夠看出來每一個線程包括主線程訪問到的threadInfo獲取到的值都是不同的,並且存放的信息就是本線程的信息,也應證了上面那句話ThreadLocal存儲的變量屬於當前線程多線程

3、ThreadLocal原理

3.1 ThreadLocal的存取過程

解析原理先從源碼開始,首先看一下ThreadLocal.set()方法ide

//  ThreadLocal中set方法
public void set(T value) { // 獲取當前線程對象 Thread t = Thread.currentThread(); // 獲取該線程的threadLocals屬性(ThreadLocalMap對象) ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } // Thread類中的threadLocals定義 ThreadLocal.ThreadLocalMap threadLocals = null; // ThreadLocal中getMap方法 ThreadLocalMap getMap(Thread t) { return t.threadLocals; } // ThreadLocal中createMap方法 // 爲線程建立ThreadLocalMap對象並賦值給threadLocals void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); } 

從源碼中看到在set方法裏面就是把值存入ThreadLocalMap類中,這個類是屬於ThreadLocal的內部類,可是在Thread類中也有定義threadLocals變量,get方法的操做對象也是ThreadLocalMap,也就是說關鍵的存儲和獲取實質上在於ThreadLocalMap類。其中是以ThreadLocal類爲key,存入的值爲value,而ThreadLocal又是定義在每一個線程的屬性中,這也就實現了「ThreadLocal線程私有化」的功能,每次都是先從當前線程獲取到threadLocals屬性,也就是得到ThreadLocalMap對象,以ThreadLocal對象做爲key存取對應的值工具

3.2 探究ThreadLocalMap對象

ThreadLocalMap對象是ThreadLocal類的內部類,其中它就是一個簡單的Map,每一個存的值被封裝成Entry進行存儲,下面是Entry的源碼學習

static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } 

Entry是ThreadLocalMap的內部類,仔細觀察其源碼發現,它是繼承了一個ThreadLocal的弱引用。回憶一下Java中的四種引用:強引用、軟引用、弱引用、幻象引用。強引用是new建立出來的對象,只要強引用存在,垃圾收集器永遠不會回收該引用;軟引用(SoftReference)是在內存即將被佔滿時被回收;弱引用(WeakReference)用來描述非必需對象的,可是它的強度比軟引用更弱一些,被弱引用關聯的對象只能生存到下一次GC發生以前,當垃圾收集器工做時,不管當前內存是否足夠,都會回收掉該類對象;幻象引用又稱虛引用或幽靈引用(Phantom Reference),它是最弱的一種引用關係。一個對象是否有虛引用的存在,徹底不會對其生存時間構成影響,也沒法經過虛引用來取得對象實例,任什麼時候候均可能被回收。更多的解釋和示例讀者可前往我以前寫的這篇文章閱讀:this

3.3 ThreadLocal對象的回收

Entry是一個弱引用,是由於它不會影響到ThreadLocal的GC行爲,若是是強引用的話,在線程運行過程當中,咱們再也不使用ThreadLocal了,將ThreadLocal置爲null,但ThreadLocal在線程的ThreadLocalMap裏還有引用,致使其沒法被GC回收。而Entry聲明爲WeakReference,ThreadLocal置爲null後,線程的ThreadLocalMap就不算強引用了,ThreadLocal就能夠被GC回收了。

//  ThreadLocal中的remove方法
public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); } // ThreadLocalMap中的remove方法 private void remove(ThreadLocal<?> key) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { if (e.get() == key) { e.clear(); expungeStaleEntry(i); return; } } } 

可能存在的內存泄漏問題

ThreadLocalMap的生命週期是與線程同樣的,可是ThreadLocal卻不必定,可能ThreadLocal使用完了就想要被回收,可是此時線程可能不會當即終止,還會繼續運行(好比線程池中線程重複利用),若是ThreadLocal對象只存在弱引用,那麼在下一次垃圾回收的時候必然會被清理掉。

若是ThreadLocal沒有被外部強引用的狀況下,在垃圾回收的時候會被清理掉的,這樣一來ThreadLocalMap中使用這個ThreadLocal的key也會被清理掉。可是,value 是強引用,不會被清理,這樣一來就會出現key爲null的value

在ThreadLocalMap中,調用 set()、get()、remove()方法的時候,會清理掉key爲null的記錄。在ThreadLocal設置爲null以後,ThreadLocalMap中存在key爲null的值,那麼就可能發生內存泄漏,只有手動調用remove()方法來避免,因此咱們在使用完ThreadLocal變量時,儘可能用threadLocal.remove()來清除,避免threadLocal=null的操做。remove方法是完全地回收該對象,而經過threadLocal=null只是釋放掉了ThreadLocal的引用,可是在ThreadLocalMap中卻還存在其Entry,後續還需處理。

4、ThreadLocal應用場景

  • 處理較爲複雜的業務時,使用ThreadLocal代替參數的顯示傳遞。
  • ThreadLocal能夠用來作數據庫鏈接池保存Connection對象,這樣就可讓線程內屢次獲取到的鏈接是同一個了(Spring中的DataSource就是使用的ThreadLocal)。
  • 管理Session會話,將Session保存在ThreadLocal中,使線程處理屢次處理會話時始終是一個Session。
相關文章
相關標籤/搜索