ThreadLocal源碼分析
一.概述
ThreadLocal用於線程之間的變量共享,每一個線程都有自己獨立的副本,用來存儲自己的變量。只要線程是活躍的,其對應的副本就是可以訪問的。當線程死亡,其副本會作爲垃圾被回收。
ThreadLocal.java中的相關代碼:
public class ThreadLocal<T> {
…
}
1.泛型T爲存儲和獲取數據的類型。
二.源碼分析
1.全局變量
ThreadLocal.java中的相關代碼:
// ThreadLoacal的哈希值,用來區分不同線程的ThreadLocal
private final int threadLocalHashCode = nextHashCode();
// 用於計算哈希值,採用AtomicInteger保證併發時安全,初始值爲0
private static AtomicInteger nextHashCode = new AtomicInteger();
// 用於計算哈希值
private static final int HASH_INCREMENT = 0x61c88647;
2. nextHashCode方法
用於產生ThreadLocal的哈希值。
ThreadLocal.java中的相關代碼:
private static int nextHashCode() {
// 將nextHashCode的值加上HASH_INCREMENT的值,並返回
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
3.構造方法
ThreadLocal.java中的相關代碼:
public ThreadLocal() {
}
4. initialValue方法
用於爲ThreadLocal提供當前線程的初始化值。
默認爲空,如果有需要,可以重寫該方法。
ThreadLocal.java中的相關代碼:
protected T initialValue() {
return null;
}
5. getMap方法
獲取指定線程的ThreadLocalMap。
ThreadLocal.java中的相關代碼:
ThreadLocalMap getMap(Thread t) {
// 返回線程的全局變量
return t.threadLocals;
}
6. createMap方法
爲指定線程創建ThreadLocalMap用於保存線程間共享的對象,並存儲firstValue。
ThreadLocal.java中的相關代碼:
void createMap(Thread t, T firstValue) {
// 創建ThreadLocalMap對象
// 保存到線程的全局變量threadLocals中
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
7. createInheritedMap方法
該方法用於根據父線程的ThreadLocalMap對象創建子線程繼承的ThreadLocalMap對象。
ThreadLocal.java中的相關代碼:
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
// 調用構造方法,創建新對象
return new ThreadLocalMap(parentMap);
}
8. childValue方法
該方法在InheritableThreadLocal類中被重寫,用於根據父值而給出子值。
該方法爲了在必要時繼承的子類可以不用重寫ThreadLocalMap類,而通過此方法實現原本的功能。
ThreadLocal.java中的相關代碼:
T childValue(T parentValue) {
throw new UnsupportedOperationException();
}
9. remove方法
刪除當前線程在ThreadLocal中存儲的值。
ThreadLocal.java中的相關代碼:
public void remove() {
// 獲取當前線程的ThreadLocalMap對象
ThreadLocalMap m = getMap(Thread.currentThread());
// 若ThreadLocalMap不爲空
if (m != null)
m.remove(this); // 移除當前線程存儲的值
}
10. set方法
保存值到當前線程的ThreadLocalMap中。
ThreadLocal.java中的相關代碼:
public void set(T value) {
// 獲取當前線程
Thread t = Thread.currentThread();
// 獲取當前線程的ThreadLocalMap對象
ThreadLocalMap map = getMap(t);
// 若ThreadLocalMap對象存在
if (map != null)
// 直接保存value
map.set(this, value);
else // 若ThreadLocalMap對象不存在
// 則創建ThreadLocalMap對象,並保存value
createMap(t, value);
}
11. setInitialValue方法
用於將當前線程的ThreadLocalMap存儲的值設置爲初始值。
ThreadLocal.java中的相關代碼:
private T setInitialValue() {
// 獲取初始值
T value = initialValue();
// 獲取當前線程
Thread t = Thread.currentThread();
// 獲取當前線程的ThreadLocalMap對象
ThreadLocalMap map = getMap(t);
// 若ThreadLocalMap對象存在
if (map != null)
map.set(this, value); // 則保存值value
else // 若ThreadLocalMap對象不存在
createMap(t, value); // 則創建ThreadLocalMap對象,再保存值value
// 返回初始值
return value;
}
12. get方法
獲取當前線程的ThreadLocalMap中對應的值。
ThreadLocal.java中的相關代碼:
public T get() {
// 獲取當前線程
Thread t = Thread.currentThread();
// 獲取當前線程的ThreadLocalMap對象
ThreadLocalMap map = getMap(t);
// 若ThreadLocalMap對象存在
if (map != null) {
// 嘗試獲取當前ThreadLocal對應的鍵值對
ThreadLocalMap.Entry e = map.getEntry(this);
// 若鍵值對存在,說明之前存儲過
if (e != null) {
@SuppressWarnings("unchecked")
// 獲取鍵值對的值
T result = (T)e.value;
// 返回
return result;
}
}
// 若ThreadLocalMap對象不存在,
// 則將ThreadLocal對應的值設置爲初始值,並返回
return setInitialValue();
}
13.ThreadLocal中的靜態內部類ThreadLocalMap
ThreadLocalMap是ThreadLocal用於存儲鍵值對的內部類,ThreadLocal中對數據操作都是通過該內部類提供的方法實現的。
ThreadLocal.java中的相關代碼:
static class ThreadLocalMap {
…
}
1)全局變量
ThreadLocal.java中的相關代碼:
// 初始化容量,表示能夠存儲鍵值對的數量,必須是2的次方形式
private static final int INITIAL_CAPACITY = 16;
// 用於存儲鍵值對的數組
private Entry[] table;
// 用於記錄已經存儲的鍵值對的數量
private int size = 0;
// 擴容的閾值,存儲的鍵值對數量超過該值將會觸發擴容,默認爲0
private int threshold;
2)ThreadLocalMap中的靜態內部類Entry
該類用於存儲鍵值對,其中鍵的類型爲ThreadLocal,值的類型爲Object。
同時,該類繼承了弱引用類WeakReference,當獲取鍵爲空時,說明該鍵的引用被回收。
ThreadLocal.java中的相關代碼:
static class Entry extends WeakReference<ThreadLocal<?>> {
// 線程中存儲的值
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
3)構造方法
i)參數爲鍵值對
該方法會創建ThreadLocalMap,並將參數中的鍵值對進行保存。
ThreadLocal.java中的相關代碼:
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
// 根據初始化容量,創建鍵值對數組
table = new Entry[INITIAL_CAPACITY];
// 獲取ThreadLocal的哈希值,和INITIAL_CAPACITY – 1進行與運算
// 計算鍵值對在數組中的位置
// INITIAL_CAPACITY – 1作爲掩碼
// 如16-1=15,1111,最後進行與運算得到的數值一定在0到15之間
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
// 創建新的鍵值對,保存到數組對應的位置中
table[i] = new Entry(firstKey, firstValue);
// 記錄當前鍵值對數量1
size = 1;
// 設置擴容閾值
setThreshold(INITIAL_CAPACITY);
}
ii)參數爲ThreadLocalMap
該方法用來根據繼承的ThreadLocalMap的內容創建新的ThreadLocalMap。
該方法是私有的,僅供createInheritedMap方法調用。
ThreadLocal.java中的相關代碼:
private ThreadLocalMap(ThreadLocalMap parentMap) {
// 獲取繼承的ThreadLocalMap中的鍵值對數組
Entry[] parentTable = parentMap.table;
// 獲取數組長度
int len = parentTable.length;
// 根據長度設置擴容閾值
setThreshold(len);
// 根據長度創建新數組
table = new Entry[len];
// 循環,將鍵值對轉移到新數組中
for (int j = 0; j < len; j++) {
// 獲取鍵值對
Entry e = parentTable[j];
// 若j位置鍵值對存在
if (e != null) {
@SuppressWarnings("unchecked")
// 獲取鍵
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
// 若鍵存在
if (key != null) {
// 根據鍵,獲取值
Object value = key.childValue(e.value);
// 根據鍵和值,創建新的鍵值對
Entry c = new Entry(key, value);
// 根據鍵ThreadLocal的哈希值
// 計算出鍵值對在數組中的位置h
// len - 1作爲掩碼
// 如16-1=15,1111,
// 最後進行與運算得到的數值一定在0到15之間
int h = key.threadLocalHashCode & (len - 1);
// 循環,若數組的h位置有鍵值對
// 則根據當前位置h和數組長度,獲取下一個位置
// 直到找到一個沒有鍵值對的位置
while (table[h] != null)
h = nextIndex(h, len);
// 將鍵值對存在對應位置
table[h] = c;
// 當前存儲的鍵值對數量加1
size++;
}
}
}
}
4)setThreshold方法
用來設置擴容閾值。
ThreadLocal.java中的相關代碼:
private void setThreshold(int len) {
// 閾值爲數組長度的三分之二
threshold = len * 2 / 3;
}
5)nextIndex方法
獲取位置i的下一個位置。
ThreadLocal.java中的相關代碼:
private static int nextIndex(int i, int len) {
// 若i的下一個位置i+1小於數組長度len
// 則返回i+1,否則返回0
// 該操作相當於i對len取模
return ((i + 1 < len) ? i + 1 : 0);
}
6)prevIndex方法
獲取位置i的上一個位置。
ThreadLocal.java中的相關代碼:
private static int prevIndex(int i, int len) {
// 若i的上一個位置i-1大於0
// 則返回i-1,否則返回len-1
return ((i - 1 >= 0) ? i - 1 : len - 1);
}
7)expungeStaleEntry方法
刪除指定位置的鍵值對,並對其它位置的鍵值對重新散列整理。
ThreadLocal.java中的相關代碼:
private int expungeStaleEntry(int staleSlot) {
// 獲取數組
Entry[] tab = table;
// 獲取數組的長度
int len = tab.length;
// 刪除指定位置的鍵值對
tab[staleSlot].value = null;
tab[staleSlot] = null;
// 存儲的鍵值對數量減1
size--;
// 對數組循環整理,重新散列,
// 直到遇到爲空的位置,停止循環
Entry e;
int i;
// 從staleSlot下一個位置i開始
// 若i位置的鍵值對e爲空,則跳出循環
// 每次循環i向後移動一個位置,若移動到最後一個位置,則回到0開始
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
// 從i位置的鍵值對e中獲取鍵k
ThreadLocal<?> k = e.get();
// 若鍵爲空
if (k == null) {
// 清除當前位置的鍵值對
e.value = null;
tab[i] = null;
// 存儲的鍵值對數量減1
size--;
} else { // 若鍵不爲空
// 重新計算鍵值對應該存放的位置h
int h = k.threadLocalHashCode & (len - 1);
// 若鍵值對應該存儲的位置和當前存放的位置不一致
if (h != i) {
// 對當前的位置進行清空
tab[i] = null;
// 循環,從鍵值對應該存放的位置h開始找
// 找到一個空位置,存放鍵值對
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
// 返回跳出循環時鍵值對爲空的位置
return i;
}
8)cleanSomeSlots方法
從位置i的下一位開始,向下查看log2(n)個位置,如果發現位置不對的鍵值對,則對其進行清理。
ThreadLocal.java中的相關代碼:
private boolean cleanSomeSlots(int i, int n) {
// 標誌位,如果進行過清理,則爲true
boolean removed = false;
// 獲取鍵值對數組
Entry[] tab = table;
// 獲取數組長度
int len = tab.length;
// 循環
do {
// 獲取位置i的下一個位置,結果再賦給i
i = nextIndex(i, len);
// 獲取該位置的鍵值對
Entry e = tab[i];
// 若該位置的鍵值對存在,並且鍵值對的鍵不存在
if (e != null && e.get() == null) {
// 重新設置循環次數
n = len;
// 設置標誌位
removed = true;
// 刪除指定位置的鍵值對,並進行整理
i = expungeStaleEntry(i);
}
} while ( (n >>>= 1) != 0); // 循環log2(n)次
// 返回結果
return removed;
}
9)expungeStaleEntries方法
刪除數組中鍵不存在的鍵值對,對位置錯誤的鍵值對進行調整。
ThreadLocal.java中的相關代碼:
private void expungeStaleEntries() {
// 獲取鍵值對數組
Entry[] tab = table;
// 獲取數組長度
int len = tab.length;
// 循環遍歷鍵值對數組
for (int j = 0; j < len; j++) {
// 獲取鍵值對
Entry e = tab[j];
// 若鍵值對存在,並且鍵值對的鍵不存在
if (e != null && e.get() == null)
// 刪除該位置的鍵值對,並整理。
expungeStaleEntry(j);
}
}
10)resize方法
用於對鍵值對數組擴容,擴大之前容量的2倍。
ThreadLocal.java中的相關代碼:
private void resize() {
// 獲取舊的鍵值對數組
Entry[] oldTab = table;
// 獲取舊的鍵值對數組的長度
int oldLen = oldTab.length;
// 根據舊長度計算新長度,新長度爲舊長度的2倍
int newLen = oldLen * 2;
// 根據新長度創建新數組
Entry[] newTab = new Entry[newLen];
// 用於記錄新數組使用的空間數量
int count = 0;
// 循環遍歷舊數組
for (int j = 0; j < oldLen; ++j) {
// 取出鍵值對
Entry e = oldTab[j];
// 若鍵值對存在
if (e != null) {
// 獲取鍵值對的鍵
ThreadLocal<?> k = e.get();
// 若鍵不存在
if (k == null) {
// 則將其值刪除,即釋放引用
e.value = null; // 可以幫助垃圾回收器快點回收
} else { // 若鍵存在
// 重新計算其在新數組中的位置
int h = k.threadLocalHashCode & (newLen - 1);
// 若該位置已經被佔了,則循環查找下一個可以存放的位置
while (newTab[h] != null)
h = nextIndex(h, newLen);
// 存放鍵值對到新數組中
newTab[h] = e;
// 新數組已經使用的空間數量加1
count++;
}
}
}
// 設置新的擴容閾值
setThreshold(newLen);
// 將局部變量保存到全局變量
// 將已經使用的空間數量保存到全局變量
size = count;
// 將新數組保存到全局變量
table = newTab;
}
11)rehash方法
對所有的鍵值對進行整理,刪除鍵不存在的鍵值對。
如果整理後鍵值對的數量還是過高,則進行擴容。
ThreadLocal.java中的相關代碼:
private void rehash() {
// 整理所有鍵值對。
expungeStaleEntries();
// 當數組已經使用的空間數量超過閾值的四分之三
// 即總空間的六分之一,則進行擴容
// 使用更低的閾值避免滯後現象
if (size >= threshold - threshold / 4)
resize();
}
12)replaceStaleEntry方法
該方法用於在set方法中將staleSlot位置處的鍵值對替換成鍵爲key值爲value的鍵值對。
ThreadLocal.java中的相關代碼:
private void replaceStaleEntry(ThreadLocal<?> key, Object value, int staleSlot) {
// 獲取鍵值對數組
Entry[] tab = table;
// 獲取鍵值對數組的長度
int len = tab.length;
Entry e;
// slotToExpunge用來標記需要進行刪除的位置
int slotToExpunge = staleSlot;
// 循環向前查找,找到一個鍵值對存在,
// 但鍵值對中鍵不存在(被回收)的位置,直到遇到空的鍵值對結束循環
for (int i = prevIndex(staleSlot, len);
(e = tab[i]) != null;
i = prevIndex(i, len))
if (e.get() == null)
slotToExpunge = i; // 用slotToExpunge標記該位置
// 循環向後查找鍵值對,直到遇到空的鍵值對結束循環
for (int i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
// 若鍵相等
// 即本來要插入在staleSlot位置,但現在在i位置找到了對應的鍵
if (k == key) {
// 修改鍵的值爲新值
e.value = value;
// 交換i位置和staleSlot位置的鍵值對
tab[i] = tab[staleSlot];
tab[staleSlot] = e;
// 若二者相等,說明之前循環向前查找沒有找到要找的鍵值對
if (slotToExpunge == staleSlot)
// 標記需要刪除的位置i,
// i位置的鍵值對已經更新並轉移到staleSlot位置
slotToExpunge = i;
// 刪除i位置的鍵值對,並對鍵值對數組進行整理
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
// 返回
return;
}
// 若k爲空同時slotToExpunge和staleSlot相等
// k爲空說明,當前的i位置是滿足鍵值對存在但鍵不存在的位置
// slotToExpunge和staleSlot相等說明該位置之前向前查找時沒有找到
if (k == null && slotToExpunge == staleSlot)
// 標記需要刪除的位置i
slotToExpunge = i;
}
// 代碼執行到此,說明循環中沒有找到存在的鍵值對,
// 該鍵值對之前沒有存儲過
// 刪除staleSlot位置的值(釋放引用,加速垃圾回收)
tab[staleSlot].value = null;
// 創建新鍵值對並存儲
tab[staleSlot] = new Entry(key, value);
// 若slotToExpunge和staleSlot不相等
// 說明在循環向後查找中找到了需要刪除的鍵值對
if (slotToExpunge != staleSlot)
// 刪除slotToExpunge位置的鍵值對,並進行整理
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
13)remove方法
移除鍵爲key的鍵值對。
ThreadLocal.java中的相關代碼:
private void remove(ThreadLocal<?> key) {
// 獲取鍵值對數組
Entry[] tab = table;
// 獲取鍵值對數組長度
int len = tab.length;
// 計算鍵值對應該存儲的位置
int i = key.threadLocalHashCode & (len-1);
// 從i位置循環向後查找,直到遇到空鍵值對結束循環
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
// 若i位置的鍵值對的將和要移除的鍵值對的鍵相等
if (e.get() == key) {
// 釋放鍵值對的引用(加速垃圾回收)
e.clear();
// 刪除位置i的鍵值對e
expungeStaleEntry(i);
// 返回
return;
}
}
}
14)set方法
添加鍵值對。
ThreadLocal.java中的相關代碼:
private void set(ThreadLocal<?> key, Object value) {
// 獲取鍵值對數組
Entry[] tab = table;
// 獲取鍵值對數組長度
int len = tab.length;
// 計算鍵值對應該存儲的位置i
int i = key.threadLocalHashCode & (len-1);
// 循環從位置i向後查找,直到遇到空鍵值對跳出循環
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
// 獲取鍵值對的鍵
ThreadLocal<?> k = e.get();
// 若i位置鍵值對的鍵k和要添加的鍵值對的鍵key相等
// 說明之前添加過
if (k == key) {
// 更新鍵值對的值爲新的值value
e.value = value;
// 返回
return;
}
// 若當前位置i的鍵值對的鍵不存在(被回收)
if (k == null) {
// 將新的鍵值對添加到位置i處
replaceStaleEntry(key, value, i);
// 返回
return;
}
}
// 代碼執行到此處,說明該鍵值對之間沒有添加過
// 並且也沒有合適的位置
// 創建新的鍵值對,保存到位置i
tab[i] = new Entry(key, value);
// 當前存儲的鍵值對數量加1,並將其值賦給sz
int sz = ++size;
// 若整理空間失敗,同時當前存儲的鍵值對數量超過了擴容閾值
// 說明當前存儲空間不足
if (!cleanSomeSlots(i, sz) && sz >= threshold)
// 重新散列,刪除鍵不存在的鍵值對,必要時將進行擴容
rehash();
}
三.總結
1.ThreadLocal是用於在線程中存儲數據,其內部是通過ThreadLocalMap來存儲的,每一個線程都有一個對應ThreadLocalMap,ThreadLocalMap的內部維護了一個Entry數組。
2.一個Entry就是一個鍵值對,該鍵值對鍵的類型爲ThreadLocal,值的類型爲Object。其中,由於Entry繼承了WeakReference,因此每個鍵值對Entry對鍵ThreadLocal的引用都是弱引用。
3.每個ThreadLocal對象被創建時,都有不同的哈希值,該值會通過計算得到以該ThreadLocal爲鍵的鍵值對的存儲位置。當我們調用ThreadLocal的get和set方法,會將以該ThreadLocal爲鍵的鍵值對存儲到當前線程的ThreadLocalMap中。
4.ThreadLocalMap存儲在線程中,但是對ThreadLocalMap的操作(如:創建、整理、刪除、添加、獲取…)須要通過ThreadLocal對象的方法來實現。
5.ThreadLocalMap內部提供方法,會對鍵被回收的鍵值對進行清理,以保證空間存儲需要的鍵值對,當清理後的空間還是不足時,會觸發擴容。ThreadLocalMap的初始容量爲16,每次擴容會會擴大之前的2倍。