在ThreadLocal的get(),set()的時候都會清除線程ThreadLocalMap裡所有key為null的value。
而ThreadLocal的remove()方法會先将Entry中對key的弱引用斷開,設定為null,然後再清除對應的key為null的value。
本文分析get方法
ThreadLocal類的get方法
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t); // 擷取線程t中的ThreadLocalMap
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this); // 擷取entry,見代碼1
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue(); // 沒有找到對應的值,調用setInitialValue方法并傳回初始值,見代碼4
}
關鍵邏輯就是去目前線程的ThreadLocalMap中擷取對應此ThreadLocal對象的entry,如果擷取到了就傳回entry的value。否則傳回調用setInitialValue方法的結果。
代碼1
ThreadLocal.ThreadLocalMap類的getEntry方法
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - );
Entry e = table[i];
if (e != null && e.get() == key) // 在key計算hash的位置上直接命中查詢,直接傳回該entry
return e;
else
return getEntryAfterMiss(key, i, e); // 沒有直接命中,調用getEntryAfterMiss,見代碼2
}
如果在key計算hash的位置上直接命中查詢,直接傳回該entry,否則調用getEntryAfterMiss并傳回結果。
代碼2
ThreadLocal.ThreadLocalMap類的getEntryAfterMiss方法
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) { // 從i位置開始周遊,尋找key能對應上的entry
ThreadLocal<?> k = e.get();
if (k == key)
return e;
if (k == null)
expungeStaleEntry(i); // 遇到key為null的entry,調用expungeStaleEntry方法,見代碼3
else
i = nextIndex(i, len);
e = tab[i];
}
return null; // 實在沒有找到,隻能傳回null了
}
在從第i個entry向後周遊的過程中,找到對應的key的entry就直接傳回,如果遇到key為null的entry,則調用expungeStaleEntry方法進行清理。
代碼3
ThreadLocal.ThreadLocalMap類的expungeStaleEntry方法
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// expunge entry at staleSlot
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--; // 以上代碼,将entry的value指派為null,這樣友善GC時将真正value占用的記憶體給釋放出來;将entry指派為null,size減1,這樣這個slot就又可以重新存放新的entry了
// Rehash until we encounter null
Entry e;
int i;
for (i = nextIndex(staleSlot, len); // 從staleSlot後一個index開始向後周遊,直到遇到為null的entry
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == null) { // 如果entry的key為null,則清除掉該entry
e.value = null;
tab[i] = null;
size--;
} else {
int h = k.threadLocalHashCode & (len - );
if (h != i) { // key的hash值不等于目前的index,說明該entry是因為有哈希沖突導緻向後移動到目前index位置的
tab[i] = null;
// Unlike Knuth 6.4 Algorithm R, we must scan until
// null because multiple entries could have been stale.
while (tab[h] != null) // 對該entry,重新進行hash并解決沖突
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i; // 傳回經過整理後的,位于staleSlot位置後的第一個為null的entry的index值
}
expungeStaleEntry方法不止清理了staleSlot位置上的entry,還把staleSlot之後的key為null的entry都清理了,并且順帶将一些有哈希沖突的entry給填充回可用的index中。
代碼4
ThreadLocal類的setInitialValue方法
private T setInitialValue() {
T value = initialValue(); // initialValue()方法直接傳回null
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value); // 調用ThreadLocalMap的set方法
else
createMap(t, value); // 建立新的ThreadLocalMap,并将value添加進去
return value;
}
setInitialValue方法裡面,真正有難度的就是在map不為null時要去調用set方法了。這種情況會在key(也就是ThreadLocal對象)對應的entry已經被清理過後出現,也有可能是一個沒有設定過值的ThreadLocal對象來調用get方法,就會進入到這層邏輯。關于ThreadLocalMap的set方法,在另一篇筆記ThreadLocal源碼分析:(一)set(T value)方法中有分析過了,這裡就不再貼了。
ThreadLocal的get方法,也可能會觸發ThreadLocalMap的清理方法,将ThreadLocalMap中key為null的entry給清理掉,友善GC來回收記憶體。
系列文章連結:
ThreadLocal源碼分析:(一)set(T value)方法
ThreadLocal源碼分析:(二)get()方法
ThreadLocal源碼分析:(三)remove()方法