引言
ThreadLocal 可以在每個線程中存取數據,并且不同的線程中的數據互不影響。使用在數據以線程為作用域并且不同的線程擁有不用的數據副本,或者是復雜的參數傳遞時(參數在同一線程中的不同類中傳遞)。
在分析消息機制源碼的時候,涉及到了 ThreadLocal,使用是在 Looper 類中通過 ThreadLocal<Looper> 對象的 set 方法和 get 方法存取當前線程的 Looper 對象
我們發現 ThreadLocal 對象是一個靜態的對象,說明每個線程都可以通過該對象來存取當前線程對應的 Looper ,說明 ThreadLooper 中存放的數據確實是以線程為作用域的,源碼接著看
源碼分析
看源碼之前,先大體的說一下工作的原理。每個線程類 Thread 中都保存了一個 ThreadLocalMap ,可以理解為就是一個 Map,Map 的 key 就是這個 Thread 中所有使用到的 ThreadLocal 對象,Map 的 value 是 Thread 中使用該 ThreadLocal 存儲的數據的值。在使用時通過 ThreadLocal 即可存取對應的 value。
可以這么理解。但是 ThreadlocalMap 并不是一個 Map,其內部通過一個 Entry 類型的數組 Entry[] 來存儲 ThreadLocal 和其存儲的值,Entry 中保存了 value 和 ThreadLocal,Entry 是繼承了 WeakReference ,也就是說 Entry[] 是以軟弱引用來保存 Entry 的,并且數組中的索引是通過 ThreadLocal 的 hashCode 計算的值,這樣根據 ThreadLocal 的 hashCode 也就有了與 Entry[] 中每個索引位置的值之間的關系。
ThreadLocalMap 存在的意義不僅僅是保存 Entry[] 數組,也為該數組中數據的存取提供了更多計算方法
// Entry 中只保存了 value,super 方法中會保存 ThreadLocal
static class Entry extends WeakReference<ThreadLocal> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}
set() 方法用于數據的存儲
// ThreadLocal 的 set() 方法
public void set(T value) {
Thread t = Thread.currentThread(); // 獲取當前線程
ThreadLocalMap map = getMap(t); // 從線程中取出 ThreadLocalMap
if (map != null)
map.set(this, value); // 如果 ThreadLocalMap 不為 null ,則使用 ThreadLocalMap 存儲
else
createMap(t, value); // 如果 ThreadLocalMap 為 null,則為該線程初始化 ThreadLocalMap ,并將數據存儲
}
// ThreadLocal 的 getMap() 方法
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
// ThreadLocalMap 的 set() 方法
private void set(ThreadLocal key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1); // 根據 ThreadLocal 計算在 Entry[] 數組中的索引值
// 如果該索引位置有值,則判斷 ThreadLocal 是否匹配,不匹配則遍歷到下一有值位置
for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
ThreadLocal k = e.get();
// 如果有值則判斷當前位置的 ThreadLocal 和需要插入的 ThreadLocal 是否相同,如果相同則直接修改 value 為新值
if (k == key) {
e.value = value;
return;
}
// 如果對應位置 ThreadLocal 為空,則取代舊的 Entry ,重新賦值新的 Entry
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 的 createMap() 方法
void createMap(Thread t, T firstValue) { // Thread 的 ThreadLocalMap 為 null 時,為 Thread 創建新的 ThreadLocal 并將 ThreadLocal 及 Value 存儲
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
// ThreadLocalMap 的構造方法
ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY]; // 初始化 Entry[]
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); // 根據 ThreadLocal 計算索引值
table[i] = new Entry(firstKey, firstValue); 為該索引位置賦值
size = 1;
setThreshold(INITIAL_CAPACITY);
}
通過在 Thread 中獲取 ThreadLocalMap ,再根據 TheadLocal 和 Value 將數據存儲到了 Entry[] 數組中,具體的存儲過程看注釋。
有一個關鍵點,存儲的時候,如果 ThreadLocal 對應的位置有值,則會查看向下一個位置,如果該位置還是有值則繼續向下一位置,直到沒有值的位置插入該 Entry
存:判斷 ThreadLocalMap 是否為空
為空:創建 ThreadLocalMap 并為對應位置賦值
-
不為空:判斷當前位置是否有值 Entry
- 無值:為當前位置賦值為新的 Entry
- 有值:判斷 ThreadLocal 是否對應
- 對應:修改舊 Value 值
- 不對應:向下一有值索引位置判斷,直到 ThreadLocal 對應(修改原 Value 值) 或遇到該索引對應值無 ThreadLocal 則為該位置賦值新 Entry,或遇到無值索引時為該位置賦值新 Entry
get() 方法源碼解析
// ThreadLocal 的 get() 方法
public T get() {
Thread t = Thread.currentThread(); // 獲取當前線程 Thread
ThreadLocalMap map = getMap(t); // 獲取該線程中的 ThreadLocalMap
if (map != null) { // 如果 ThreadLocalMap 不為空
ThreadLocalMap.Entry e = map.getEntry(this); 調用 ThreadLocalMap 的 getEntry 方法獲取 Entry 對象
if (e != null) // Entry 不為空則將 Entry 中存儲的 Value 返回
return (T)e.value;
}
return setInitialValue(); // 線程的 ThreadLocalMap 為空或者 ThreadLocalMap 中無對應 Entry 情況
}
// ThreadLocalMap 的 getEntry() 方法
private Entry getEntry(ThreadLocal key) {
int i = key.threadLocalHashCode & (table.length - 1); // 計算在 Entry[] 中的索引
Entry e = table[i];
if (e != null && e.get() == key) // 如果 Entry 不為 null 且當前位置對應的 ThreadLocal 為該 ThreadLocal 則返回 Entry
return e;
else
return getEntryAfterMiss(key, i, e); // 該位置不對應則向下一位置遍歷查詢
}
// ThreadLocalMap 的 getEntryAfterMiss() 方法
// Entry 為 null 或 ThreadLocal 不對應則向下一個位置遍歷,直到 Entry 的 ThreadLocal 和當前 ThreadLocal 對應或遍歷結束,如果遍歷結束還是 null 則返回 null
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 的 setInitialValue() 方發法,線程的 ThreadLocalMap 為空或者 ThreadLocalMap 中無對應 Entry 情況
private T setInitialValue() {
T value = initialValue(); // 該方法默認返回 null,可重新該方法修改默認值
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value); // ThreadLocalMap 不為空則為該位置創建值為默認值的 Entry
else
createMap(t, value); // ThreadLocalMap 為空時,則為該線程創建 ThreadLocalMap 并未該位置創建值為默認值的 Entry
return value; // 將默認值返回
}
get() 方法主要為從 ThreadLocalMap 中取出對應的值,在該位置沒有值或當前線程無對應 ThreadLocalMap 情況下,會返回默認的值 null
取,判斷 ThreadLocalMap 是否為空
-
為空
- 創建新 ThreadLocalMap 并將該位置賦值 Value 為默認值的 新 Entry ,并返回
-
不為空
- 根據 ThreadLocal 計算索引,判斷該索引位置是否有值
- 無值:為該索引位置賦值 Value 為默認值的 新 Entry ,并返回
- 有值:判斷 ThreadLocal 是否匹配
- 匹配:返回對應位置 Value
- 不匹配:遍歷下一位置直到 ThreadLocal 匹配時返回該 Entry,或該位置的 Entry 的 ThreadLocal 為空時刪除該位置之后的所有空條目,繼續向下一位置遍歷,遍歷結束如果還是沒有對應 ThreadLocal 則返回 null
- 根據 ThreadLocal 計算索引,判斷該索引位置是否有值