ThreadLocal詳解

1、簡(jiǎn)介

ThreadLocal是什么呢?其實(shí)ThreadLocal并非是一個(gè)線程的本地實(shí)現(xiàn)版本,它并不是一個(gè)Thread,而是threadlocalvariable(線程局部變量)。也許把它命名為T(mén)hreadLocalVar更加合適。線程局部變量(ThreadLocal)其實(shí)的功用非常簡(jiǎn)單,就是為每一個(gè)使用該變量的線程都提供一個(gè)變量值的副本,是Java中一種較為特殊的線程綁定機(jī)制,是每一個(gè)線程都可以獨(dú)立地改變自己的副本,而不會(huì)和其它線程的副本沖突。

2、Spring中應(yīng)用

Spring使用ThreadLocal解決線程安全問(wèn)題。一般情況下,只有無(wú)狀態(tài)的Bean才可以在多線程環(huán)境下共享,在Spring中,絕大部分Bean都可以聲明為singleton作用域。就是因?yàn)镾pring對(duì)一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非線程安全狀態(tài)采用ThreadLocal進(jìn)行處理,讓它們也成為線程安全的狀態(tài),因?yàn)橛袪顟B(tài)的Bean就可以在多線程中共享了。

3、Slf4j 日志輸出中的應(yīng)用

Java Web項(xiàng)目中,通常使用實(shí)現(xiàn)了 Slf4j 的Logback或Log4j來(lái)進(jìn)行日志輸出,Slf4j 中定義了 MDC 接口,要求實(shí)現(xiàn)多線程間日志隔離,Logback 和 Log4j 正是利用ThreadLocal來(lái)實(shí)現(xiàn)的。更多內(nèi)容將會(huì)在另一篇專(zhuān)門(mén)介紹MDC的文章中講解。

4、實(shí)現(xiàn)原理

ThreadLocal起作用的根源是Thread類(lèi):

public class Thread implements Runnable {
   ......(其他源碼)
    /* 
     * 當(dāng)前線程的ThreadLocalMap,主要存儲(chǔ)該線程自身的ThreadLocal
     * 本文主要討論的就是這個(gè)ThreadLocalMap
     */
    ThreadLocal.ThreadLocalMap threadLocals = null;

    /*
     * InheritableThreadLocal,自父線程集成而來(lái)的ThreadLocalMap,
     * 主要用于父子線程間ThreadLocal變量的傳遞
     * 此處我們不過(guò)多解釋inheritableThreadLocals變量
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
    ......(其他源碼)
}

正是由于Thread中有 ThreadLocal.ThreadLocalMap 變量,ThreadLocal才得以使用。
下面來(lái)看一下ThreadLocal工作原理。
ThreadLocal的set()方法的源碼是:

    /**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     */
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

在這個(gè)方法內(nèi)部我們看到,首先通過(guò)getMap(Thread t)方法獲取一個(gè)和當(dāng)前線程相關(guān)的ThreadLocalMap,然后將變量的值設(shè)置到這個(gè)ThreadLocalMap對(duì)象中,當(dāng)然如果獲取到的ThreadLocalMap對(duì)象為空,就通過(guò)createMap方法創(chuàng)建。

線程隔離的秘密,就在于ThreadLocalMap這個(gè)類(lèi)。ThreadLocalMap是ThreadLocal類(lèi)的一個(gè)靜態(tài)內(nèi)部類(lèi),它實(shí)現(xiàn)了鍵值對(duì)的設(shè)置和獲取(對(duì)比Map對(duì)象來(lái)理解),每個(gè)線程中都有一個(gè)獨(dú)立的ThreadLocalMap副本,它所存儲(chǔ)的值,只能被當(dāng)前線程讀取和修改。ThreadLocal類(lèi)通過(guò)操作每一個(gè)線程特有的ThreadLocalMap副本,從而實(shí)現(xiàn)了變量訪問(wèn)在不同線程中的隔離。因?yàn)槊總€(gè)線程的變量都是自己特有的,完全不會(huì)有并發(fā)錯(cuò)誤。還有一點(diǎn)就是,ThreadLocalMap存儲(chǔ)的鍵值對(duì)中的鍵是this對(duì)象指向的ThreadLocal對(duì)象,而值就是你所設(shè)置的對(duì)象了。

    /**
     * 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;
    }

    /**
     * Create the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the map
     */
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

再看一下ThreadLocal類(lèi)中的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);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

再來(lái)看setInitialValue()方法:

    /**
     * Variant of set() to establish initialValue. Used instead
     * of set() in case user has overridden the set() method.
     *
     * @return the initial value
     */
    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

獲取和當(dāng)前線程綁定的值時(shí),ThreadLocalMap對(duì)象是以this指向的ThreadLocal對(duì)象為鍵進(jìn)行查找的,這當(dāng)然和前面set()方法的代碼是相呼應(yīng)的。

5、內(nèi)存泄漏


Threadlocal里面使用了一個(gè)存在弱引用的map,當(dāng)釋放掉threadlocal的強(qiáng)引用以后,map里面的value卻沒(méi)有被回收.而這塊value永遠(yuǎn)不會(huì)被訪問(wèn)到了. 所以存在著內(nèi)存泄露.

5.1、為什么使用弱引用

要理解為什么ThreadLocalMap中需要使用WeakReference作為key類(lèi)型,那么首先需要理解WeakReference的意義。

WeakReference是Java語(yǔ)言規(guī)范中為了區(qū)別直接的對(duì)象引用(程序中通過(guò)構(gòu)造函數(shù)聲明出來(lái)的對(duì)象引用)而定義的另外一種引用關(guān)系。WeakReference標(biāo)志性的特點(diǎn)是:reference實(shí)例不會(huì)影響到被應(yīng)用對(duì)象的GC回收行為(即只要對(duì)象被除WeakReference對(duì)象之外所有的對(duì)象解除引用后,該對(duì)象便可以被GC回收),只不過(guò)在被對(duì)象回收之后,reference實(shí)例想獲得被應(yīng)用的對(duì)象時(shí)程序會(huì)返回null。

If you have a ThreadLocal as a final class member, that's a strong reference, and it cannot be collected until the class is unloaded. But this is how any class member works, and isn't considered a memory leak.

理解了WeakReference之后,ThreadLocalMap使用它的目的也相對(duì)清晰了:當(dāng)threadLocal實(shí)例可以被GC回收時(shí),系統(tǒng)可以檢測(cè)到該threadLocal對(duì)應(yīng)的Entry是否已經(jīng)過(guò)期(根據(jù)reference.get() == null來(lái)判斷,如果為true則表示過(guò)期,程序內(nèi)部稱為stale slots)來(lái)自動(dòng)做一些清除工作,否則如果不清除的話容易產(chǎn)生內(nèi)存無(wú)法釋放的問(wèn)題:value對(duì)應(yīng)的對(duì)象即使不再使用,但由于被threadLocalMap所引用導(dǎo)致無(wú)法被GC回收。

5.2、最佳實(shí)踐

ThreadLocalMap會(huì)在set,get以及resize等方法中對(duì)stale slots做自動(dòng)刪除(set以及get不保證所有過(guò)期slots會(huì)在操作中會(huì)被刪除,而resize則會(huì)刪除threadLocalMap中所有的過(guò)期slots)。

       /**
         * Get the entry associated with key.  This method
         * itself handles only the fast path: a direct hit of existing
         * key. It otherwise relays to getEntryAfterMiss.  This is
         * designed to maximize performance for direct hits, in part
         * by making this method readily inlinable.
         */
        private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            if (e != null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);//此處著手清除key為null的Entry
        }

最好的做法是將調(diào)用threadlocal的remove方法:把當(dāng)前ThreadLocal從當(dāng)前線程的ThreadLocalMap中移除。(包括key,value)

6、總結(jié)

ThreadLocal使用場(chǎng)合主要解決多線程中數(shù)據(jù)數(shù)據(jù)因并發(fā)產(chǎn)生不一致問(wèn)題。ThreadLocal為每個(gè)線程的中并發(fā)訪問(wèn)的數(shù)據(jù)提供一個(gè)副本,通過(guò)訪問(wèn)副本來(lái)運(yùn)行業(yè)務(wù),這樣的結(jié)果是耗費(fèi)了內(nèi)存,單大大減少了線程同步所帶來(lái)性能消耗,也減少了線程并發(fā)控制的復(fù)雜度。
ThreadLocal不能使用基本數(shù)據(jù)類(lèi)型,只能使用Object類(lèi)型。

參考文獻(xiàn)

  1. ThreadLocal可能引起的內(nèi)存泄露
  2. ThreadLocal與WeakReference
  3. ThreadLocal Resource Leak and WeakReference
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • ThreadLocal在java.lang包中,其主要作用是提供一個(gè)和線程綁定的變量環(huán)境,即通過(guò)ThreadLoc...
    charming_coder閱讀 1,369評(píng)論 1 8
  • ThreadLocal 多線程在并發(fā)執(zhí)行時(shí),需要數(shù)據(jù)共享,因此才有了 volatile 變量解決多線程數(shù)據(jù)之間可見(jiàn)...
    聰明的奇瑞閱讀 748評(píng)論 0 5
  • ThreadLocal是一個(gè)關(guān)于創(chuàng)建線程局部變量的類(lèi)。 通常情況下,我們創(chuàng)建的變量是可以被任何一個(gè)線程訪問(wèn)并修改的...
    icecrea閱讀 837評(píng)論 0 2
  • 1. 概念 ThreadLocal 用于提供線程局部變量,在多線程環(huán)境可以保證各個(gè)線程里的變量獨(dú)立于其它線程里的變...
    zly394閱讀 2,047評(píng)論 0 1
  • 文:呂一品 -1- 小攀是我培訓(xùn)班的同學(xué),其貌不揚(yáng),帶著厚厚的眼鏡,穿著小棉襖。培訓(xùn)的第一天我最后一個(gè)進(jìn)教室,其它...
    呂一品閱讀 511評(píng)論 0 1

友情鏈接更多精彩內(nèi)容