ThreadLocal中的WeakReference,threadlocal

來源:互聯網
上載者:User

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()才會被回收

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.