天天看點

ThreadLocal源碼分析:(二)get()方法

在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()方法