ThreadLocal中的WeakReference


 

在一般的網站開發中,基於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變量可能導致內存溢出的問題:

 Entry弱引用了ThreadLocal,因此Entry是否存活,不會影響ThreadLocal的生命周期,ThreadLocal在沒有其他對象引用后被回收,但是它對應的Entry中的value實際還在被Entry引用,而這不是一個弱引用,如果不清掉掉Entry,value就被一直強引用,無法釋放,那么就可能會內存溢出

如果一直沒有ThreadLocal變量訪問,並且線程一直存活,就不會清理陳舊Entry,value永遠無法釋放,這也是某種意義上的內存泄露

 

一直以來,我把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對象上的

 

也就是說,如果Thread死掉后,它和ThreadLocal對象也就沒有任何關系了,這對我們的程序沒有任何影響,因為我們也沒有機會再取它在Thread中的副本了;

而ThreadLocal變量被設置為null后,雖然Thread中的Entry弱引用了ThreadLocal,即使Thread對象還在,當ThreadLocal對象沒有被其他對象引用后,它就可以被GC調了,Thread對象產生了弱引用,不會影響到ThreadLocal變量的回收

 

其他知識點:

需要注意的是,當一個線程執行Thread.start()后,如果start()方法已經執行完畢了,雖然Thread對象還在,其實它的生命周期已經結束了,對應的ThreadLocal變量可以被回收

如果線程再次調用Thread.start()方法,會拋出異常,因此不用關注線程再次被啟動,因為它已經無法再被啟動了(PS:線程復用技術並非是重新start,而是在線程內執行多個任務)

 

以下情況ThreadLocal變量是永遠不會回收的

public class FlagContext {

    /**
     * ThreadLocal變量
     */
    private final static ThreadLocal<Boolean> tbFlag = new ThreadLocal<Boolean>();
}

FlagContext的靜態變量引用了ThreadLocal實例tbFlag,tbFlag是個final 聲明的變量,永遠不會為空,因此,這種情況下,tbFlag對應的值,只會在線程生命周期結束,或調用tbFlag.remove()才會被回收!原因呢:

 ThreadLocal永遠被FlagContext強引用了,而Thread和ThreadLocal不管是否是弱引用,即使Thread中的Entry被回收了,也不會被GC掉!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM