ThreadLocal 原理分析

eaglelihh發表於2021-01-25

用法

ThreadLocal<String> threadLocal = new ThreadLocal<>(); // 無初始值
ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> "123"); // 有初始值

threadLocal.set("123"); // set操作
threadLocal.get(); // get操作
threadLocal.remove(); // remove操作
一個小例子:

public static void main(String[] args) throws InterruptedException {
    ThreadLocal threadLocal = new InheritableThreadLocal();
    threadLocal.set("Hello");
    System.out.println("現線上程是" + Thread.currentThread().getName() + ", 嘗試獲取:" + threadLocal.get());
    new Thread(() -> {
        threadLocal.set("World");
        System.out.println("現線上程是" + Thread.currentThread().getName() + ", 嘗試獲取:" + threadLocal.get());
        threadLocal.remove();
    }).start();
    Thread.sleep(3000);
    System.out.println("現線上程是" + Thread.currentThread().getName() + ", 嘗試獲取:" + threadLocal.get());
    threadLocal.remove();
}

輸出:
現線上程是main, 嘗試獲取:Hello
現線上程是Thread-0, 嘗試獲取:World
現線上程是main, 嘗試獲取:Hello

實現

set操作

public void set(T value) {
    Thread t = Thread.currentThread(); // 獲取當前執行緒
    ThreadLocalMap map = getMap(t); // 獲取ThreadLocalMap
    if (map != null)
        map.set(this, value); 
    else
        createMap(t, value); // 建立map
}

// getMap
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals; 
}
public class Thread implements Runnable {
    ThreadLocal.ThreadLocalMap threadLocals = null;
}

// createMap
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    table = new Entry[INITIAL_CAPACITY]; // INITIAL_CAPACITY = 16;
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); // 計算下標
    table[i] = new Entry(firstKey, firstValue);
    size = 1;
    setThreshold(INITIAL_CAPACITY); // 設定閾值
}

// 雜湊值
private final int threadLocalHashCode = nextHashCode();
private static int nextHashCode() {
    return nextHashCode.getAndAdd(HASH_INCREMENT); // HASH_INCREMENT = 0x61c88647;
}

// ThreadLocalMap資料結構
static class ThreadLocalMap {
    private Entry[] table;

    static class Entry extends WeakReference<ThreadLocal<?>> {
        Object value;
        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
}

 private void set(ThreadLocal<?> key, Object value) {
    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1); // 下標

    // 使用線性探測法來解決雜湊衝突
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get(); // 獲取弱引用ThreadLocal

        if (k == key) { // 對於已經存在的key,直接賦值
            e.value = value;
            return;
        }

        if (k == null) { // 弱引用ThreadLocal為null,進行替換
            replaceStaleEntry(key, value, i);
            return;
        }
    }

    tab[i] = new Entry(key, value); // 上面兩種情況不是,直接賦值
    int sz = ++size; // size+1
    if (!cleanSomeSlots(i, sz) && sz >= threshold) // 清除無效entry,大於閾值,擴容並重新雜湊化
        rehash();
}

get操作

public T get() {
    Thread t = Thread.currentThread(); // 獲取當前執行緒
    ThreadLocalMap map = getMap(t); // 獲取ThreadLocalMap
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this); // 傳入自己,也就是threadlocal物件,得到entry
        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]; // 取值
    if (e != null && e.get() == key) // 正好取到,直接返回
        return e;
    else
        return getEntryAfterMiss(key, i, e);
}
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) // 弱引用ThreadLocal為null,清除無效的entry
            expungeStaleEntry(i); // 
        else
            i = nextIndex(i, len); // 下一個
        e = tab[i];
    }
    return null;
}

remove操作

public void remove() {
   ThreadLocalMap m = getMap(Thread.currentThread());
   if (m != null)
       m.remove(this);
}

private void remove(ThreadLocal<?> key) {
    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        if (e.get() == key) {
            e.clear(); // 弱引用的引用置為null
            expungeStaleEntry(i); // 清除entry,並重新雜湊化
            return;
        }
    }
}

public void clear() {
    this.referent = null;
}

private int expungeStaleEntry(int staleSlot) {
    Entry[] tab = table;
    int len = tab.length;

    // 槽置為null
    tab[staleSlot].value = null;
    tab[staleSlot] = null;
    size--;

    // 後面的進行重新雜湊化
    Entry e;
    int i;
    for (i = nextIndex(staleSlot, len);
         (e = tab[i]) != null;
         i = nextIndex(i, len)) {
        ThreadLocal<?> k = e.get();
        if (k == null) {
            e.value = null;
            tab[i] = null;
            size--;
        } else {
            int h = k.threadLocalHashCode & (len - 1);
            if (h != i) {
                tab[i] = null;

                while (tab[h] != null)
                    h = nextIndex(h, len);
                tab[h] = e;
            }
        }
    }
    return i;
}

記憶體洩露

當ThreadLocal沒有強依賴,ThreadLocal會在下一次發生GC時被回收,key是被回收了,但是value卻沒有被回收,為了防止這個問題出現,最好手動呼叫remove方法。

相關文章