簡介
ThreadLocal和Synchronized一樣,都是用於處理線程間變量問題;後者有用於等待方式處理變量,前者用多個副本處理對象,時間和空間犧牲;那麼ThreadLocal內部是如何用副本的形式管理的呢?繼續往下看
ThreadLocal一般使用
ThreadLocal<String> t = new ThreadLocal<>();
t.set("name");
一般就是上面的用法,我們看看代碼內部是如何實現的?
public ThreadLocal() {
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
set設置值時,會getMap()獲取一個ThreadLocalMap,如果這個map是一個null的話,就需要創建一個ThreadLocalMap,那麼他是如創建的呢?
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);
}
在createMap中就是new了一個ThreadLocalMap,但是要注意他把這個map賦值給了t,也就是Thread,所以如果是同一個線程裏面有多個ThreadLocal對象,其實是用同一個ThreadLocalMap
ThreadLocalMap內部結構,是一個Entry數組,通過ThreadLocal的hashcode確定在數組中的位置,然後key是ThreadLocal,value是設置的值;那這個Entry又是什麼結構呢?
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
從上可以知道,Entry的key實質是一個弱引用,也就是Map持有的ThreadLocal是一個弱引用,map的持有並不影響對它的GC,但是value會影響,map被Thread持有,而Thread幾乎可以作爲GC Root的根節點(根搜索法),很有可能造成內存泄漏
比如這種情況就會有內存泄漏,當前Thread線程存活時間很久,並且有多個ThreadLocal成員,如果其中一個ThreadLocal不用了,那麼這個local的value由於Thread存活很久而造成內存泄漏
官方也意識到了這個問題,修改了部分源碼,在對ThreadLocal調用get時候會自動清楚掉Entry數組中key爲null的value,這樣就不會造成內存泄漏了;
但是還是可能會出現,一種就是粗心的程序員沒有去get;另一種是把ThreadLocal設置爲static屬性
總結
綜上所述,同一個線程中的多個ThreadLocal共用一個Entry的Table;不同線程有自己獨立的table;爲了不造成內存泄漏,使用完後get或者remove掉,以防出現內存泄漏