【原始碼學習】ThreadLocal

2020-10-14 11:00:29

大概介紹

ThreadLocal通俗說法:執行緒的本地變數,通過為每個執行緒建立副本的方式解決執行緒隔離問題,實現執行緒中的變數傳遞
比如:

  • 一個老專案,要從單執行緒的改為多執行緒實現了,那麼對於一些變數,不是執行緒共用的,就要用ThreadLocal包裝起來
  • 每個執行緒對應一個請求連線,在這個執行緒中的多個類、方法都要用到這個請求的使用者資訊,可見這個資訊不是線上程間共用的,那麼用ThreadLocal把使用者資訊包裝起來

ThreadLocalMap

  • ThreadLocalMap是ThreadLocal的一個內部靜態類,Thread類裡面有兩個ThreadLocal.ThreadLocalMap型別的內部物件,分別為threadLocals、inheritableThreadLocals。threadLocals是執行緒獨佔的變數,子執行緒可以讀到父執行緒inheritableThreadLocals變數的值。
  • ThreadLocalMap維護著一堆key是ThreadLocal型別,value隨便的Entry。也就是說,Thread類的threadLocals物件有一個內部的Entry[]型別的table變數,table的每一個Entry的key是ThreadLocal物件,value隨便。
  • ThreadLocal<?>型別的成員是一個弱參照,其特點是,當參照元素無強參照時,JVM GC時會立即回收參照元素。劃重點!!!這裡,知道弱參照怎麼寫了嗎?!!!!
static class ThreadLocalMap {
	static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;

        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
    private Entry[] table;
    // 初始化ThreadLocalMap
    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
      table = new Entry[INITIAL_CAPACITY];
       int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
       table[i] = new Entry(firstKey, firstValue);
       size = 1;
       setThreshold(INITIAL_CAPACITY);
   }
}

方法

public T get();
public void set(T value);
public void remove();
protected T initialValue();

1. initialValue方法

預設的initialValue是返回null,你也可以在建立ThreadLocal變數的時候重寫該函數。

// 預設的
protected T initialValue() {
        return null;
    }
// 重寫initialValue函數的實現
private static final ThreadLocal<Map<Charset, CharsetDecoder>> decoders =
    new ThreadLocal<Map<Charset, CharsetDecoder>>()
    {
        @Override
        protected Map<Charset, CharsetDecoder> initialValue()
        {
            return new IdentityHashMap<>();
        }
    };

2. get方法

首先獲取當前的執行緒,然後獲取當前執行緒的threadLocals,也就是執行緒獨佔變數,通過map.getEntry(this)獲取Entry,這裡的this代表一個ThreadLocal物件,即entry的key,返回這個entry的value。

ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null)
            return (T)e.value;
    }
    return setInitialValue();
}

3. set方法

這個同get,不解釋了

public void set(T value) {  
	Thread t = Thread.currentThread();  
	ThreadLocalMap map = getMap(t);  
	if (map != null)  
	    map.set(this, value);  
	else  
	    createMap(t, value);  
} 

4. remove方法

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

記憶體漏失

ThreadLocalMap的key ThreadLocal是弱參照的原因:

  1. 如果使用強參照,則key永遠都不會被回收,生命週期和執行緒一樣
  2. 使用強參照,一旦key被回收,則就只剩ThreadLocalMap中的弱參照了,會在下次gc的時候被回收。
  3. 如果key被回收了,那麼對應的value沒有被回收,為了避免因此造成的記憶體漏失,每次get()、set()、remove() ThreadLocalMap中的值的時候,會自動清理key為null的value。
  4. 一個比較好的做法是,在不需要的時候,手動remove掉。