ThreadLocal幾個要點

1 前言

ThreadLocal是java中的一個經常使用類,一般用來爲當前線程存儲變量。java

2 建立

有三種建立方式:數組

  • 1 直接建立:ThreadLocal theadLocal = new ThreadLocal<>();
  • 2 建立時實現initialValue方法:
    ThreadLocal有一個initialValue方法,默認返回null,供子類建立時實現:
    protected T initialValue() {
        return null;
    }
    複製代碼
    因此能夠在建立時實現initialValue,以達到初始化數據的做用:
    public final static ThreadLocal<Object> theadLocal = new ThreadLocal<Object>(){
        @Override
        protected Object initialValue() {
            return new Object();
        }
    };
    複製代碼
  • 3 ThreadLocal.withInitial: ThreadLocal的一個靜態方法,經過一個Supplier在建立時初始化:
    public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
        return new SuppliedThreadLocal<>(supplier);
    }
    複製代碼
    直接用withInitial來建立ThreadLocal:
    public static final ThreadLocal<Object> current = ThreadLocal.withInitial(() -> {
        return new Object();
    });
    複製代碼

3 實現

3.1 ThreadLocalMap

每一個Thread對象中,有一個ThreadLocal.ThreadLocalMap對象:ide

ThreadLocal.ThreadLocalMap threadLocals = null;
複製代碼

ThreadLocal的操做(set/get/remove),就是對當前對象的ThreadLocal.ThreadLocalMap對象的操做:this

Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
複製代碼

3.2 Entry

Entry是ThreadLocalMap內部存儲數據的節點:spa

  • 1 Entry繼承了WeakReference,referent爲Entry的key(ThreadLocal對象);
  • 2 Entry的key是ThreadLocal對象。
static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
}
複製代碼

ThreadLocalMap中聲明瞭Entry數組,做爲數據的存儲:線程

private static final int INITIAL_CAPACITY = 16;

private Entry[] table;

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            // 實例化Entry數組
            table = new Entry[INITIAL_CAPACITY];
            // 計算數組下標
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }
複製代碼

數組的下標,由ThreadLocal.threadLocalHashCode作相關位運算後肯定。code

ThreadLocal.threadLocalHashCode在每一個對象實例化時計算:對象

private final int threadLocalHashCode = nextHashCode();

private static AtomicInteger nextHashCode = new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;

private static int nextHashCode() {
    return nextHashCode.getAndAdd(HASH_INCREMENT);
}
複製代碼

4 移除

  • 1 取到當前線程的ThreadLocalMap,調用ThreadLocalMap.remove:
public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
    }
複製代碼
  • 2 根據key(ThreadLocal對象)找到對應的Entry,並執行Reference.clear()
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) {
                    // 找到key,移除
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }
複製代碼

5 總結

remove時,清除的只是key(ThreadLocal對象),並無清除value,爲防止內存泄露,建議:繼承

  • 1 顯式的調用remove(),並在remove前,先經過get獲取value,而後對value進行清理;
  • 2 聲明ThreadLocal子類,並重寫remove()方法:在調用super.remove()前,先對value進行統一處理。
相關文章
相關標籤/搜索