ThreadLocal

LZC發表於2020-07-17

什麼是ThreadLocal?它是屬於執行緒自己的小倉庫。也就是在堆中建立一個執行緒自己才能訪問到的物件,利用執行緒封閉來確保安全。

3907960-e67947664a4e144a.png
圖片來自佔小狼

使用時會建立一個ThreadLocal靜態變數,然後在方法中呼叫set,第一次呼叫set時會建立一個ThreadLocalMap物件,將其賦給執行緒的ThreadLocal.ThreadLocalMap threadLocals

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

    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
//-----------------------------------------------------
    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);
    }

ThreadLocalMap是ThreadLocal的靜態內部類,ThreadLocalMap裡有一個Entry的靜態內部類。這三者啥關係?儲存key,value的是Entry,ThreadLocalMap封裝Entry實現主要功能,ThreadLocal對ThreadLocalMap進一步封裝,使得呼叫非常簡便;
現在想想上面說的當你第一次呼叫set後,堆中是什麼情況?

  • 堆中一個ThreadLocal例項,它是靜態的所以只會存在一個,多個執行緒共享它。
  • 每個執行緒都會有一個自己的ThreadLocalMap例項,分析一下它們的可達性,只有本執行緒的threadLocals變數指向它,也就是說當執行緒執行完畢,執行緒的ThreadLocalMap例項會被GC清除。
  • 每個執行緒還會在堆中建立只有自己可達的Entry陣列物件,ThreadLocalMap裡的table指向它,它的key是ThreadLocal例項,所以當執行緒銷燬,Entry仍有可達性,ThreadLocal是靜態的那Entry就永遠不會被銷燬了,OOM!所以引入了弱引用WeakReference,將Entry裡對ThreadLocal引用設為弱引用,當只有弱可達性時,Entry會被清理;

如上面所說的,那麼要是Thread一直不銷燬呢?仍然會發生OOM!
如何防止?ThreadLocal在set,get方法呼叫時都會主動清理Entry[]中key為null的entry;在使用時一個良好的習慣便是主動清理,

try{
  //do
}finally{
  threadLocal.remove();
}

這樣便能避免OOM;

我們從執行緒安全方面想想,為什麼不直接在方法中建立一個倉庫例項,它也是利用執行緒封閉,安全,不就不用這麼麻煩了嗎?那麼當你想在其它地方獲取儲存值時只能將它傳遞過去,這就相當於你將這個例項釋出出去了,這就存在安全隱患,而且傳遞來傳遞去非常不方便;我們再來看看ThreadLocal的設計,如前面分析的一樣,它非常巧妙,讓人感嘆啊!

其他方面比如ThreadLocal利用hash來定位,用線性探測法來解決hash衝突。

相關文章