目錄java
ThreadLocal顧名思義理解爲線程本地變量,這個變量只在這個線程內,對於其餘的線程是隔離的,JDK中對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提供了線程局部變量,只能經過ThreadLocal的set方法和get方法來存儲和得到變量。數據結構
ThreadLocal類結構以下:多線程
能夠看到ThreadLocal有內部類ThradLocalMap,ThreadLocal存儲線程局部對象就是利用了ThreadLocalMap數據結構,在下面的源碼分析也會先從這裏開始。ide
ThreadLocalMap靜態內部類Entry是存儲鍵值對的基礎,Entry類繼承自WeakReference(爲何用弱引用在後面解釋),經過Entry的構造方法代表鍵值對的鍵只能是ThreadLocal對象,值是Object類型,也就是咱們存儲的線程局部對象,經過super調用父類WeakReference構造函數將ThreadLocal<?>對象轉換成弱引用對象
ThreadMap存儲鍵值對的原理與HashMap是相似的,HashMap依靠的是數組+紅黑樹數據結構和哈希值映射,ThreadMap依靠Entry數組+散列映射,ThreadLocalMap使用了Entry數組來保存鍵值對,Entry數組的初始長度爲16,鍵值對到Entry數組的映射依靠的是int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
,經過ThreadLocal對象的threadLocalHashCode與(INITIAL_CAPACITY - 1)按位相與將鍵值對均勻散列到Entry數組上。函數
static class ThreadLocalMap { // 鍵值對對象 static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } //初始Entry數組大小 private static final int INITIAL_CAPACITY = 16; //Entry數組 private Entry[] table; //ThreadLocalMap實際存儲鍵值對的個數 private int size = 0; //數組擴容閾值 private int threshold; // Default to 0 //閾值爲數組長度的2/3 private void setThreshold(int len) { threshold = len * 2 / 3; } private static int nextIndex(int i, int len) { return ((i + 1 < len) ? i + 1 : 0); } /** * Decrement i modulo len. */ private static int prevIndex(int i, int len) { return ((i - 1 >= 0) ? i - 1 : len - 1); } //構造一個ThreadLocalMap對象,並把傳入的第一個鍵值對存儲 ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { table = new Entry[INITIAL_CAPACITY]; int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); table[i] = new Entry(firstKey, firstValue); size = 1; setThreshold(INITIAL_CAPACITY); } }
ThreadLocal做爲作爲鍵值對的鍵經過常量threadLocalHashCode
映射到Entry數組,threadLocalHashCode
初始化時會調用nextHashCode()
方法,就是在nextHashCode
的基礎上加上0x61c88647
,實際上每一個ThreadLocal
對象的threadLocalHashCode
值相差0x61c88647
,這樣生成出來的Hash值能夠較爲均勻的散列到2的冪次方長度的數組中,具體可見這篇文章爲何使用0x61c88647
因爲採用的是散列算法,就須要考慮Hash衝突的狀況,HashMap解決Hash衝突的方法是鏈表+紅黑樹,ThreadLocalMap解決方法是linear-probe(線性探測),簡單來講若是散列對應的位置已經有鍵值對佔據了,就把散列位置加/減一找到符合條件的位置放置鍵值對。源碼分析
// final常量,一旦肯定再也不改變 private final int threadLocalHashCode = nextHashCode(); /** * The next hash code to be given out. Updated atomically. Starts at * zero. */ private static AtomicInteger nextHashCode = new AtomicInteger(); /** * The difference between successively generated hash codes - turns * implicit sequential thread-local IDs into near-optimally spread * multiplicative hash values for power-of-two-sized tables. */ private static final int HASH_INCREMENT = 0x61c88647; /** * Returns the next hash code. */ private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT); } //構造方法 public ThreadLocal() { }
簡要介紹完了內部類ThreadLocalMap後,set方法屬於ThreadLocal,首先得到與線程Thread綁定的ThreadLocalMap對象,再將ThreadLocal和傳入的value封裝爲Entry鍵值對存入ThreadLocalMap中。注意,ThreadLocalMap對象是在線程Thread中聲明的:
ThreadLocal.ThreadLocalMap threadLocals = null;
post
public void set(T value) { //得到當前線程對象 Thread t = Thread.currentThread(); //得到線程對象的ThreadLocalMap ThreadLocalMap map = getMap(t); // 若是map存在,則將鍵值對存到map裏面去 if (map != null) map.set(this, value); //若是不存在,調用ThreadLocalMap構造方法存儲鍵值對 else createMap(t, value); } //返回線程t中聲明的Thread ThreadLocalMap getMap(Thread t) { return t.threadLocals; } void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
private void set(ThreadLocal<?> key, Object value) { Entry[] tab = table; int len = tab.length; //利用ThreadLocal的threadLocalHahsCode值散列 int i = key.threadLocalHashCode & (len-1); //若是散列的位置不爲空,判斷是不是哈希衝突 for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); //若是此位置的鍵值對的鍵與傳入的鍵相同,覆蓋鍵值對的值 if (k == key) { e.value = value; return; } //鍵值對的鍵爲空,說明鍵ThreadLocal對象被回收,用新的鍵值對代替過期的鍵值對 if (k == null) { replaceStaleEntry(key, value, i); return; } } //散列位置爲空,直接存儲鍵值對 tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }
得到當前線程中保存的以ThreadLocal對象爲鍵的鍵值對的值。首先獲取當前線程關聯的ThreadLocalMap,再得到以當前ThreadLocal對象爲鍵的鍵值對,map爲空的話返回初始值null,即線程局部變量爲null,學習
public T get() { //獲取與當前線程綁定的ThreadLocalMap Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); //map不爲空,獲取鍵值對對象 if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); } private Entry getEntry(ThreadLocal<?> key) { //散列 int i = key.threadLocalHashCode & (table.length - 1); Entry e = table[i]; //判斷散列位置的鍵值對是否符合條件:e.get()==key if (e != null && e.get() == key) return e; else return getEntryAfterMiss(key, i, e); } //線性探測尋找key對應的鍵值對 private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) { Entry[] tab = table; int len = tab.length; while (e != null) { ThreadLocal<?> k = e.get(); if (k == key) return e; if (k == null) expungeStaleEntry(i); else i = nextIndex(i, len); e = tab[i]; } return null; }
從ThreadLocalMap中移除鍵值對,通常在get方法取出保存的線程局部變量後調用remove方法防止內存泄露。
public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }
鍵值對對象Enry的鍵是ThreadLocal對象,可是使用WeakReferrence虛引用包裝了的,虛引用相對於咱們常用的String str = "abc"
這種強引用來講對GC回收對象的影響較小,如下是虛引用的介紹:
WeakReference是Java語言規範中爲了區別直接的對象引用(程序中經過構造函數聲明出來的對象引用)而定義的另一種引用關係。WeakReference標誌性的特色是:reference實例不會影響到被應用對象的GC回收行爲(即只要對象被除WeakReference對象以外全部的對象解除引用後,該對象即可以被GC回收),只不過在被對象回收以後,reference實例想得到被應用的對象時程序會返回null。
若是Entry的鍵使用強引用,那麼咱們存入的鍵值對即便線程以後再也不使用也不會被回收,生命週期將變得和線程的生命週期同樣。而使用了虛引用以後,做爲鍵的虛引用並不影響ThreadLocal對象被GC回收,當ThreadLocal對象被回收後,鍵值對就會被標記爲stale entry(過時的鍵值對),再下一次調用set/get/remove方法後會進行 ThreadLocalMap層面對過時鍵值對進行回收,防止發生內存泄漏。
注意:當咱們使用了set方法存入局部變量後,若是不進行get/remove,那麼過時的鍵值對沒法被回收,因此建議在get取出存儲變量後手動remove,能夠有效防止內存泄漏。
ThreadLocal實現了存儲線程局部變量,ThreadLocal的實現並非HashMap<Thread,Object>以線程對象爲鍵,而是在線程內部關聯了一個ThreadLocalMap用於存儲鍵值對,鍵值對的鍵是ThreadLocal對象,因此ThreadLocal對象自己是不存儲內容的,而是做爲鍵與存儲內容構成鍵值對。