Java ThreadLocal解析

狼爺發表於2020-05-16

簡介

ThreadLocal 類似區域性變數,解決了單個執行緒維護自己執行緒內的變數值(存、取、刪),讓執行緒之間的資料進行隔離。(InheritableThreadLocal 特例)

這裡涉及三個類,Thread、ThreadLocal、ThreadLocalMap

原始碼解析

ThreadLocal的結構圖

  1. Thread 中有一個 ThreadLocal.ThreadLocalMap 型別的變數 threadLocals。因為ThreadLocalMap變數是跟執行緒繫結的,所以不存在多執行緒共享變數之間的併發問題,所以ThreadLocal也就是執行緒安全的變數。
  2. ThreadLocalMap 是 ThreadLocal 的一個內部靜態類,沒有繼承java.util.Map,定義了一個Entry[]變數,通過Entry的get()方法作為key,value屬性作為值來實現一個類似Map的操作
  3. Entry 是 ThreadLocalMap 的一個內部靜態類,繼承WeakReference<ThreadLocal<?>>,並且定義了一個變數value(Object型別)
  4. ThreadLocal 內部封裝了getMap()、Set()、Get()、Remove()4個核心方法,用於操作ThreadLocalMap
  5. 通過getMap()獲取每個子執行緒Thread持有自己的ThreadLocalMap例項,因此它們是不存在併發競爭的
  6. ThreadLocalMap中Entry[]陣列儲存資料,初始化長度16,大於等於3/4閾值,就進行2倍擴容。
  7. ThreadLocalMap中Entry的key是對ThreadLocal的弱引用,當主執行緒拋棄掉ThreadLocal物件時,垃圾收集器會忽略這個key的引用而清理掉ThreadLocal物件, 防止了記憶體洩漏。
  8. 雜湊演算法-魔數0x61c88647,利用一定演算法實現了元素的完美雜湊

看原始碼可以得出,set、get、remove操作的都是ThreadLocalMap,key為當前執行緒,value為執行緒區域性變數快取值

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

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

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

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

問題

不呼叫remove會記憶體溢位嗎?
大部分場景下是不會的,少數場景才會。

執行時,會在棧中產生兩個引用,指向堆中相應的物件。
可以看到,ThreadLocalMap使用ThreadLocal的弱引用作為key,這樣一來,假設當ThreadLocal ref和ThreadLocal之間的強引用斷開時,即ThreadLocal ref被置為null,下一次GC時,threadLocal物件勢必會被回收。
這樣,ThreadLocalMap中就會出現key為null的Entry,就沒有辦法訪問這些key為null的Entry的value,如果當前執行緒再遲遲不結束的話,比如使用執行緒池,執行緒使用完成之後會被放回執行緒池中,不會被銷燬,這些key為null的Entry的value就會一直存在一條強引用鏈:
Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永遠無法回收,造成記憶體洩漏。

參考資料

https://www.cnblogs.com/dennyzhangdd/p/7978455.html ThreadLocal終極原始碼剖析
https://liwx2000.iteye.com/blog/1774169 ThreadLocal會記憶體溢位嗎
https://www.jianshu.com/p/cdb2ea3792b5 深入理解Java弱引用

相關文章