threadLocal詳解

前言

之前遇到的一個(gè)筆試題:簡(jiǎn)單介紹threadLocal夭咬,并解釋為什么其不會(huì)造成內(nèi)存泄漏机打。因?yàn)閷?duì)threadLocal不是很了解冒滩,所以借此機(jī)會(huì)了解一下

出現(xiàn)原因

通常情況下溪王,任何一個(gè)線程都可以訪問和修改我們所創(chuàng)建的變量毕箍,由此增加了線程間共享變量的風(fēng)險(xiǎn)弛房。

有時(shí)可能要避免共享變量,使用ThreadLocal輔助類為各個(gè)線程提供各自的實(shí)例

例如:

SimpleDateFormat類不是線程安全的而柑。假設(shè)有一個(gè)靜態(tài)變量

public static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");

如果有兩個(gè)線程都執(zhí)行如下操作:

String dateStamp = dateFormat.format(new Date());

結(jié)果可能很混亂文捶,因?yàn)閐ateFormat使用的內(nèi)部數(shù)據(jù)結(jié)構(gòu)可能會(huì)被并發(fā)的訪問所破壞

那么針對(duì)上述,可以選擇的解決方案

  • 同步——開銷太大
  • 在需要時(shí)構(gòu)造一個(gè)局部SimpleDateFormat對(duì)象—— 太浪費(fèi)了

threadLocal介紹

可以實(shí)現(xiàn)每一個(gè)線程都有自己的專屬本地變量

ThreadLocal 類主要解決的就是讓每個(gè)線程綁定自己的值媒咳,可以將 ThreadLocal 類形象的比喻成存放數(shù)據(jù)的盒子粹排,盒子中可以存儲(chǔ)每個(gè)線程的私有數(shù)據(jù)

還是以上面的SimpleDateFormat為例,要為每個(gè)線程構(gòu)造一個(gè)實(shí)例代碼:

public static final ThreadLocal<SimpleDateFormat> dateFormat = new ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));

要訪問具體的方法涩澡,可以調(diào)用:

String dateStamp = dateFormat.get().format(new Date());

在一個(gè)給定線程中顽耳,首次調(diào)用get()方法時(shí),會(huì)調(diào)用構(gòu)造器中的lambda表達(dá)式妙同,在此之后射富,就會(huì)直接返回屬于當(dāng)前線程的實(shí)例。

常用方法

java.lang.ThreadLoacl<T> 1.2

  • T get()
    • 得到這個(gè)線程的當(dāng)前值渐溶。如果首次調(diào)用get辉浦,會(huì)調(diào)用initialize來(lái)得到這個(gè)值
  • void set(T t)
    • 為這個(gè)線程設(shè)置一個(gè)新值
  • void remove()
    • 刪除對(duì)應(yīng)這個(gè)線程的值
  • static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) 8
    • 創(chuàng)建一個(gè)線程局部變量弄抬,其初始值通過調(diào)用給定的提供者(supplier)生成

threadLocal源碼了解

Thread類中的有關(guān)變量

threadLocal定義——線程的局部變量茎辐,可以先看看Thread類中與其相關(guān)的變量:

 
public class Thread implements Runnable { 
  ......
 /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

    /*
     * InheritableThreadLocal values pertaining to this thread. This map is
     * maintained by the InheritableThreadLocal class.
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
......
}

從上面 Thread 類 源代碼可以看出 Thread 類中有一個(gè) threadLocals 和 一個(gè) inheritableThreadLocals 變量,它們都是 ThreadLocalMap 類型的變量掂恕。

其中ThreadLocalMap可以理解為ThreadLocal 類實(shí)現(xiàn)的定制化的 HashMap

默認(rèn)情況下這兩個(gè)變量都是 null拖陆,只有當(dāng)前線程調(diào)用 ThreadLocal 類的 set 或 get 方法時(shí)才創(chuàng)建它們,實(shí)際上調(diào)用這兩 個(gè)方法的時(shí)候懊亡,我們調(diào)用的是 ThreadLocalMap 類對(duì)應(yīng)的 get() 依啰、 set() 方法

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);
        //  若該線程的ThreadLocalMap對(duì)象已存在,則替換該Map里的值店枣;否則創(chuàng)建1個(gè)ThreadLocalMap對(duì)象
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

其中的getMap()方法返回的是類Thread中的threadLocals變量速警,代碼如下:

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

    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

我們可以發(fā)現(xiàn)ThreadLocal類在調(diào)用set()方法時(shí)叹誉,最終的變量并不是保存在ThreadLocal上,而是保存在當(dāng)前線程的ThreadLocalMap 中闷旧。ThreadLocal 可以理解為只是 ThreadLocalMap 的封裝长豁,傳遞了變量值

所以,ThreadLocal 內(nèi)部維護(hù)的是一個(gè)類似 Map 的 ThreadLocalMap 數(shù)據(jù)結(jié)構(gòu)忙灼, key 為當(dāng)前對(duì)象的 Thread 對(duì)象匠襟,值為 Object 對(duì)象。

 ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {...}

ThreadLocalMap 是 ThreadLocal 的靜態(tài)內(nèi)部類

ThreadLocal的get()方法

 /** 
    * 獲取ThreadLocal變量里的值
    * 由于ThreadLocal變量引用 指向 ThreadLocalMap對(duì)象该园,即獲取ThreadLocalMap對(duì)象的值 = 該線程設(shè)置的存儲(chǔ)在ThreadLocal變量的值
    **/ 
    public T get() {

        // 1. 獲得當(dāng)前線程
        Thread t = Thread.currentThread();

        // 2. 獲取該線程的ThreadLocalMap對(duì)象
        ThreadLocalMap map = getMap(t);

        // 3. 若該線程的ThreadLocalMap對(duì)象已存在酸舍,則直接獲取該Map里的值;否則則通過初始化函數(shù)創(chuàng)建1個(gè)ThreadLocalMap對(duì)象
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value; // 直接獲取值
        }
        return setInitialValue(); // 初始化
    }

/** 
    * 初始化ThreadLocal的值
    **/ 
    private T setInitialValue() {

        T value = initialValue();

        // 1. 獲得當(dāng)前線程
        Thread t = Thread.currentThread();

        // 2. 獲取該線程的ThreadLocalMap對(duì)象
        ThreadLocalMap map = getMap(t);

         // 3. 若該線程的ThreadLocalMap對(duì)象已存在里初,則直接替換該值啃勉;否則則創(chuàng)建
        if (map != null)
            map.set(this, value); // 替換
        else
            createMap(t, value); // 創(chuàng)建->>分析2
        return value;
    }

ThreadLocal如何做到線程安全

  • 每個(gè)線程擁有自己獨(dú)立的ThreadLocals變量(指向ThreadLocalMap對(duì)象 )
  • 每當(dāng)線程 訪問 ThreadLocals變量時(shí),訪問的都是各自線程自己的ThreadLocalMap變量(鍵 - 值)
  • ThreadLocalMap變量的鍵 key = 唯一 = 當(dāng)前ThreadLocal實(shí)例

上述3點(diǎn) 保證了線程間的數(shù)據(jù)訪問隔離青瀑,即線程安全

ThreadLocal 內(nèi)存泄露問題

ThreadLocalMap中使用的key是弱引用璧亮,而value是強(qiáng)引用

Java中有四種引用類型:強(qiáng)引用娇妓、軟引用故慈、弱引用、虛引用
Java設(shè)計(jì)這四種引用的主要目的有兩個(gè):

  • 可以讓程序員通過代碼的方式來(lái)決定某個(gè)對(duì)象的生命周期绰精;
  • 有利于垃圾回收哑诊。
    強(qiáng)引用:最普遍的一種引用群扶,我們寫的代碼,99.9999%都是強(qiáng)引用镀裤。只要某個(gè)對(duì)象有強(qiáng)引用與之關(guān)聯(lián)竞阐,這個(gè)對(duì)象永遠(yuǎn)不會(huì)被回收,即使內(nèi)存不足暑劝,JVM寧愿拋出OOM骆莹,也不會(huì)去回收
    弱引用:當(dāng)內(nèi)存不足,會(huì)觸發(fā)JVM的GC担猛,如果GC后幕垦,內(nèi)存還是不足,就會(huì)把軟引用的包裹的對(duì)象給干掉傅联,也就是只有在內(nèi)存不足先改,JVM才會(huì)回收該對(duì)象

所以,如果ThreadLocal 沒有被外部強(qiáng)引用的情況下蒸走,在垃圾回收的時(shí)候仇奶,key 會(huì)被清理掉,而 value 不會(huì)被清理掉比驻。這樣一來(lái)该溯, ThreadLocalMap 中就會(huì)出現(xiàn) key 為 null 的 Entry岛抄。假如我們不做任何措施的話,value 永遠(yuǎn)無(wú)法被 GC 回收狈茉,這個(gè)時(shí)候就可能會(huì)產(chǎn)生內(nèi)存泄露

ThreadLocalMap 實(shí)現(xiàn) 中已經(jīng)考慮了這種情況弦撩,在調(diào)用 set() 、 get() 论皆、 remove() 方法的時(shí)候益楼,會(huì)清理掉 key 為 null 的記錄。使用完 ThreadLocal 方法后最好手動(dòng)調(diào)用 remove() 方法

測(cè)試代碼

這里引用的是博客[3]中的代碼

 public class ThreadLocalTest {

        // 測(cè)試代碼
        public static void main(String[] args){
            // 新開2個(gè)線程用于設(shè)置 & 獲取 ThreadLoacl的值
            MyRunnable runnable = new MyRunnable();
            new Thread(runnable, "線程1").start();
            new Thread(runnable, "線程2").start();
        }

        // 線程類
        public static class MyRunnable implements Runnable {

            // 創(chuàng)建ThreadLocal & 初始化
            private ThreadLocal<String> threadLocal = new ThreadLocal<String>(){
                @Override
                protected String initialValue() {
                    return "初始化值";
                }
            };

            @Override
            public void run() {

                // 運(yùn)行線程時(shí)点晴,分別設(shè)置 & 獲取 ThreadLoacl的值
                String name = Thread.currentThread().getName();
                threadLocal.set(name + "的threadLocal"); // 設(shè)置值 = 線程名
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(name + ":" + threadLocal.get());
            }
        }
    }

參考

[1]. 《java核心技術(shù) 卷1》
[2]. 《JavaGuide面試突擊版》
[3]. Java多線程:神秘的線程變量 ThreadLocal 你了解嗎感凤?
[4]. 強(qiáng)軟弱虛引用,只有體會(huì)過了粒督,才能記住

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末陪竿,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子屠橄,更是在濱河造成了極大的恐慌族跛,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,183評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件锐墙,死亡現(xiàn)場(chǎng)離奇詭異礁哄,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)溪北,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門桐绒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人之拨,你說(shuō)我怎么就攤上這事茉继。” “怎么了蚀乔?”我有些...
    開封第一講書人閱讀 168,766評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵烁竭,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我吉挣,道長(zhǎng)派撕,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,854評(píng)論 1 299
  • 正文 為了忘掉前任听想,我火速辦了婚禮腥刹,結(jié)果婚禮上马胧,老公的妹妹穿的比我還像新娘汉买。我一直安慰自己,他們只是感情好佩脊,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評(píng)論 6 398
  • 文/花漫 我一把揭開白布蛙粘。 她就那樣靜靜地躺著垫卤,像睡著了一般。 火紅的嫁衣襯著肌膚如雪出牧。 梳的紋絲不亂的頭發(fā)上穴肘,一...
    開封第一講書人閱讀 52,457評(píng)論 1 311
  • 那天,我揣著相機(jī)與錄音舔痕,去河邊找鬼评抚。 笑死,一個(gè)胖子當(dāng)著我的面吹牛伯复,可吹牛的內(nèi)容都是我干的慨代。 我是一名探鬼主播,決...
    沈念sama閱讀 40,999評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼啸如,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼侍匙!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起叮雳,我...
    開封第一講書人閱讀 39,914評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤想暗,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后帘不,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體说莫,經(jīng)...
    沈念sama閱讀 46,465評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評(píng)論 3 342
  • 正文 我和宋清朗相戀三年寞焙,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了唬滑。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,675評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡棺弊,死狀恐怖晶密,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情模她,我是刑警寧澤稻艰,帶...
    沈念sama閱讀 36,354評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站侈净,受9級(jí)特大地震影響尊勿,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜畜侦,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評(píng)論 3 335
  • 文/蒙蒙 一元扔、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧旋膳,春花似錦澎语、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)尸变。三九已至,卻和暖如春减俏,著一層夾襖步出監(jiān)牢的瞬間召烂,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工娃承, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留奏夫,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,091評(píng)論 3 378
  • 正文 我出身青樓历筝,卻偏偏與公主長(zhǎng)得像桶蛔,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子漫谷,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評(píng)論 2 360

推薦閱讀更多精彩內(nèi)容

  • ThreadLocal是一個(gè)關(guān)于創(chuàng)建線程局部變量的類仔雷。 通常情況下,我們創(chuàng)建的變量是可以被任何一個(gè)線程訪問并修改的...
    icecrea閱讀 727評(píng)論 0 2
  • 來(lái)自:https://www.cnblogs.com/fsmly/p/11020641.html 一舔示、Thread...
    dinel閱讀 392評(píng)論 0 0
  • 前言 要了解ThreadLocal碟婆,首先搞清楚ThreadLocal 是什么?是用來(lái)解決什么問題的惕稻?ThreadL...
    薩達(dá)哈魯醬閱讀 479評(píng)論 0 7
  • 1 ThreadLocal簡(jiǎn)介 多線程訪問同一個(gè)共享變量的時(shí)候容易出現(xiàn)并發(fā)問題竖共,特別是多個(gè)線程對(duì)一個(gè)變量進(jìn)行寫入的...
    愛健身的兔子閱讀 449評(píng)論 0 0
  • 引言 ThreadLocal的官方API解釋為:“該類提供了線程局部 (thread-local) 變量。這些變量...
    小波同學(xué)閱讀 384評(píng)論 0 3