ThreadLocal中的WeakReference,threadlocal
在一般的網站開發中,基於Java的Web 架構都使用了ThreadLocal來儲存一些全域的參數,在攔截器\Filter中設定變數,讓變數可以在任意地方被擷取。
一早就瞭解到裡面有用到WeakReference(弱引用),但對弱引用僅限於一種懵懂的概念,並且認為只要GC,弱引用的對象就被回收掉了,實際情況呢?
Thread對象有一個變數名為 threadLocals 的 ThreadLocalMap對象,這個類和HashMap類似,裡面定義了一個Entry數組,不過這個Entry對象弱引用了ThreadLocal
/** * The entries in this hash map extend WeakReference, using * its main ref field as the key (which is always a * ThreadLocal object). Note that null keys (i.e. entry.get() * == null) mean that the key is no longer referenced, so the * entry can be expunged from table. Such entries are referred to * as "stale entries" in the code that follows. */ static class Entry extends WeakReference<ThreadLocal> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal k, Object v) { super(k); //調用父類建構函式,設定Reference的referent屬性 value = v; } }/** * Set the value associated with key. * * @param key the thread local object * @param value the value to be set */ private void set(ThreadLocal key, Object value) { // We don't use a fast path as with get() because it is at // least as common to use set() to create new entries as // it is to replace existing ones, in which case, a fast // path would fail more often than not. 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)]) { ThreadLocal k = e.get(); 擷取Reference的referent屬性 if (k == key) { e.value = value; return; } if (k == null) { replaceStaleEntry(key, value, i); //清理掉陳舊Entry,這種Entry是由於ThreadLocal變數對象被回收後k==null造成的 return; } } tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }
注意以下方法
if (k == null) { replaceStaleEntry(key, value, i); //清理掉陳舊Entry,這種Entry是由於ThreadLocal變數對象被回收後k==null造成的 return; }
這個地方其實也解釋了通常所說的ThreadLocal變數可能導致記憶體溢出的問題,以為ThreadLocal已經回收了,但是它對應的value實際還在被Entry引用,如果不清掉掉Entry,那麼就可能會記憶體溢出,而如果一直沒有ThreadLocal變數的訪問,並且線程一直存活,就不會清理陳舊Entry,也就可能導致記憶體溢出了
一直以來,我把WeakReference理解成:弱引用的對象一定會在下次GC時回收掉,按此推斷,ThreadLocal變數是不太安全的,因為用著用著就可能被GC掉了。
但是如果ThreadLocal有這麼嚴重的問題,誰會去用呢!
實際上對象B弱引用了A,如果A除了B以外,沒有其他引用(強、軟引用)時,才會把A GC調,這也是為什麼ThreadLocal實際上是一個安全的操作。
Thread.threadLocals 是一個Map,每個Entry都弱引用了ThreadLocal對象因此Thread.threadLocals對每個ThreadLocal對象都是弱參考關聯性
調用ThreadLocal.get()方法
/** * Returns the value in the current thread's copy of this * thread-local variable. If the variable has no value for the * current thread, it is first initialized to the value returned * by an invocation of the {@link #initialValue} method. * * @return the current thread's value of this thread-local */ public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); 可以看到get實際上是擷取當前線程的ThreadLocalMap if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) return (T)e.value; } return setInitialValue(); }/** * Get the map associated with a ThreadLocal. Overridden in * InheritableThreadLocal. * * @param t the current thread * @return the map */ThreadLocalMap getMap(Thread t) { return t.threadLocals;}
可以理解為一個ThreadLocal執行個體,在多個線程都存在副本,並且在不同線程設定的值,都不會影響到其他線程,因為ThreadLocal執行個體是儲存在當前Thread對象上的
ThreadLocal被設定為null時,由於Thread對象只是弱引用了ThreadLocal,除非ThreadLocal對象還被其他執行個體引用,否則就會被回收掉,同時需要注意的是,Thread.start()結束完後,雖然Thread對象還在,其實他的生命週期已經結束了,對應的ThreadLocal變數可以被回收,如果線程再次調用Thread.start()方法,是會拋異常的
以下情況ThreadLocal遍曆是永遠不會回收的
public class FlagContext { /** * ThreadLocal變數 */ private final static ThreadLocal<Boolean> tbFlag = new ThreadLocal<Boolean>();}
FlagContext的靜態變數引用了ThreadLocal執行個體tbFlag,tbFlag永遠不會為空白,因此,這種情況下,tbFlag對應的值,只會線上程生命週期結束,或調用tbFlag.remove()才會被回收