多線程學習筆記九之ThreadLocal
目錄
- 多線程學習筆記九之ThreadLocal
- 簡介
- 類結構
- 源碼分析
- ThreadLocalMap
- set(T value)
- get()
- remove()
- 為什麽ThreadLocalMap的鍵是WeakReferrence?
- 總結
- 簡介
多線程學習筆記九之ThreadLocal
簡介
??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數據結構,在下面的源碼分析也會先從這裏開始。
源碼分析
ThreadLocalMap
??ThreadLocalMap靜態內部類Entry是存儲鍵值對的基礎,Entry類繼承自WeakReference(為什麽用弱引用在後面解釋),通過Entry的構造方法表明鍵值對的鍵只能是ThreadLocal對象,值是Object類型,也就是我們存儲的線程局部對象,通過super調用父類WeakReference構造函數將ThreadLocal<?>對象轉換成弱引用對象
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() {
}
set(T value)
??簡要介紹完了內部類ThreadLocalMap後,set方法屬於ThreadLocal,首先獲得與線程Thread綁定的ThreadLocalMap對象,再將ThreadLocal和傳入的value封裝為Entry鍵值對存入ThreadLocalMap中。註意,ThreadLocalMap對象是在線程Thread中聲明的:
ThreadLocal.ThreadLocalMap threadLocals = null;
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);
}
- set(ThreadLocal<?> key, Object value)
??在ThreadLocalMap存在的情況下,調用ThreadLocal類的set方法存儲鍵值對,set方法需要考慮散列的位置已經有鍵值對:如果已經存在的鍵值對的鍵當存入的鍵,覆蓋鍵值對的值;如果鍵值對的鍵ThreadLocal對象已經被回收,調用replaceStaleEntry方法刪除table中所有陳舊的元素(即entry的引用為null)並插入新元素。
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();
}
get()
??獲得當前線程中保存的以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;
}
remove()
??從ThreadLocalMap中移除鍵值對,一般在get方法取出保存的線程局部變量後調用remove方法防止內存泄露。
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
為什麽ThreadLocalMap的鍵是WeakReferrence?
??鍵值對對象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對象本身是不存儲內容的,而是作為鍵與存儲內容構成鍵值對。
多線程學習筆記九之ThreadLocal