亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

深入了解Threadlocal

qiangdada / 3026人閱讀

摘要:通過將保存在中,每個線程都會擁有屬于自己的,代碼如下所示然后你就可以安心地調(diào)用了,不用考慮線程安全問題。這樣設(shè)計的好處就是,當(dāng)線程死掉之后,沒有強引用,方便收集器回收。

前言

想必大家都對Threadlocal很熟悉吧,今天我們就一起來深入學(xué)習(xí)一下。Threadlocal我更傾向于將其翻譯成線程局部變量。它有什么用處呢?Threadlocal對象通常用于防止對可變的單實例變量或全局變量進行共享。在spring中,通過將事務(wù)上下文保存在靜態(tài)的threadlocal中,當(dāng)框架代碼需要判斷當(dāng)前運行的是哪一個事務(wù)時,只需要從ThreadLocal對象中獲取事務(wù)上下文,這種機制很方便,避免了在每個方法都要傳遞上下文信息。

一個小例子

眾所周知,SimpleDateFormat不是一個線程安全的類,在多線程環(huán)境下使用同一個實例是不正確的。通過將SimpleDateFormat保存在Threadlocal中,每個線程都會擁有屬于自己的SimpleDateFormat,代碼如下所示:

public class DateFormatUtil {

    public static ThreadLocal dataFormatlocal = new ThreadLocal() {
        @Override
        protected SimpleDateFormat initialValue() {
            return new SimpleDateFormat();
        }
    };

    public static SimpleDateFormat getInstance() {
        return dataFormatlocal.get();
    }

}

然后你就可以安心地調(diào)用了,不用考慮線程安全問題。當(dāng)然你也可以在每個線程內(nèi)部調(diào)用new SimpleDateFormat(),但現(xiàn)實情況是大部分開發(fā)可能并不知道它是線程不安全的,并且將其封裝成一個單例的工具類。至于如何使用,全憑各位讀者愛好,這個例子主要用來演示Threadloca的基本用法。

腦洞大開---自己設(shè)計個threadlocal

通過之前的演示,我們已經(jīng)大致知道threadlocal怎么用,并且清楚threadlocal保證了每個線程都有一個局部變量副本。如果以此為需求,讓我們自己去設(shè)計,會是什么樣子的呢?

static Map threadlocal = new HashMap();

這就是我設(shè)計的....好吧(╯▽╰),原諒我水平有限....在這個hashMap中,threadId為key,Object為value,也是可以實現(xiàn)Threadlocal的。那么我們現(xiàn)在要來考慮幾個問題。jdk是這樣設(shè)計的嗎?這樣設(shè)計很low,但是low在哪了?
答:大師們當(dāng)然不是這樣設(shè)計了。如果這樣設(shè)計,每個線程的變量都會永久的保存在hashMap中,存在內(nèi)存泄漏。

好奇寶寶---jdk是如何實現(xiàn)threadlocal的

low的寫法我們已經(jīng)見過了,現(xiàn)在我們一起來分下jdk是如何設(shè)計的,本文引用jdk1.8。
讓我們看下threadlocal的結(jié)構(gòu)圖:

類核心方法set、get、initialValue、setInitialValue、remove,后面主要圍繞著這幾個方法介紹。

類核心變量threadLocalHashCode,nextHashCode,HASH_INCREMENT,其中nextHashCode和HASH_INCREMENT都是靜態(tài)的,所以對于一個threadlocal對象,成員變量只有一個threadLocalHashCode,這是一個自定義的hash函數(shù),主要為了減少散列桶的沖突(不是本文重點,好奇的可以看下這篇博客https://www.cnblogs.com/ilell...)。

那到底Threadlocal將變量存到哪了呢?眼尖的同學(xué)肯定已經(jīng)發(fā)現(xiàn)了ThreadLocalMap,再來看下ThreadlocalMap的結(jié)構(gòu):

相信讀過hashMap源碼同學(xué)的一定會覺得非常眼熟,ThreadlocalMap的主體也是Entry[],有resize()和rehash(),區(qū)別僅僅就是在于沒有使用鏈表結(jié)構(gòu)和紅黑樹來處理散列沖突了。Entry的結(jié)構(gòu)我們也很有必要了解一下:

Entry繼承了一個弱引用,這里簡單介紹下弱引用的知識。弱引用是用來描述非必需對象的,被弱引用關(guān)聯(lián)的對象只能生存到下次垃圾收集發(fā)生之前。無論當(dāng)前內(nèi)存是否足夠,都會回收掉只被弱引用關(guān)聯(lián)的對象。很多面試官都喜歡問Threadlocal是怎么發(fā)生內(nèi)存泄漏的,其實就是在問這個,這個需要我們對Threadlocal有一個整體的了解才能明白,所以放在最后講。

當(dāng)我們看完Threadlocal的結(jié)構(gòu)了,我們發(fā)現(xiàn)ThreadlocalMap是用來存儲的數(shù)據(jù)結(jié)構(gòu),那它是怎么和線程關(guān)聯(lián)起來的呢?這里我們開始研究Threadlocal.set()方法.

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

步驟1:獲取當(dāng)前線程
步驟2:通過當(dāng)前線程獲取ThreadLocalMap
步驟3:如果map不為null,將當(dāng)前線程和value放到map中,否則創(chuàng)建一個map。
如何和線程綁定的玄機就在getMap(t)中。

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

看到這里,我們就知道了原來Threadlocal有一個內(nèi)部類ThreadlocalMap,對于任何一個線程都會有唯一的一個ThreadlocalMap來對應(yīng),而這個map實際并不存儲在Threadlocal中,而是存在Thread當(dāng)中,只不過由Threadlocal暴露了一套api來維護Thread的ThreadLocalMap。這樣設(shè)計的好處就是,當(dāng)線程死掉之后,ThreadLocalMap沒有強引用,方便收集器回收。

繼續(xù)來看get()

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }

如果前面的流程看懂了,這就很簡單了。當(dāng)ThreadLocalMap為null或者Entry為null的時候?qū){(diào)用setInitialValue();

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

流程也很簡單,調(diào)用initialValue()初始化一個值,獲取當(dāng)前線程的ThreadLocalMap,后面的流程之前都已經(jīng)介紹過了,這里不再重復(fù),我們主要來看下initialValue()

protected T initialValue() {
        return null;
    }

請注意,protected修飾,return null;這是一個初始化值得方法,也就意味著如果業(yè)務(wù)允許的話,需要我們自己實現(xiàn)initialValue();

ThreadLocal的remove實現(xiàn)

public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

ThreadLocalMap的remove實現(xiàn)

private void remove(ThreadLocal key) {
            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)]) {
                if (e.get() == key) {
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }

都非常簡單,就留給大家自己看了~。~.

Threadlocal容易發(fā)生內(nèi)存泄露?

先回顧一下ThreadlocalMap的結(jié)構(gòu)

static class ThreadLocalMap {

        /**
         * 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 {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }
        }
        .....
        .....
}

然后套用網(wǎng)上的一張圖(實線表示強引用,虛線表示虛引用)

ThreadLocalMap使用ThreadLocal的弱引用作為key,如果一個ThreadLocal沒有外部強引用引用他,那么系統(tǒng)gc的時候,這個ThreadLocal勢必會被回收,這樣一來,ThreadLocalMap中就會出現(xiàn)key為null的Entry,就沒有辦法訪問這些key為null的Entry的value,如果當(dāng)前線程再遲遲不結(jié)束的話,這些key為null的Entry的value就會一直存在一條強引用鏈:ThreadLocal Ref -> Thread -> ThreaLocalMap -> Entry -> value永遠無法回收,造成內(nèi)存泄露。其實jdk已經(jīng)考慮到了這種情況,ThreadLocalMap的genEntry函數(shù)或者set函數(shù)會去遍歷將key為null的給移除掉,但這明顯不是所有情況都成立的,所以需要調(diào)用者自己去調(diào)用remove函數(shù),手動刪除掉需要的threadlocal,防止內(nèi)存泄露。然后jdk并不建議在棧內(nèi)聲明threadlocal,而是建議將ThreadLocal變量定義成private static的,這樣的話ThreadLocal的生命周期就更長,由于一直存在ThreadLocal的強引用,所以ThreadLocal也就不會被回收,也就能保證任何時候都能根據(jù)ThreadLocal的弱引用訪問到Entry的value值,然后remove它,防止內(nèi)存泄露。
放圖證明這是jdk說的~~

總結(jié)

再不睡覺,就天明了..

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/69665.html

相關(guān)文章

  • 深入理解Python中的ThreadLocal變量(下)

    摘要:具體怎么實現(xiàn)的呢,思想其實特別簡單,我們在深入理解中的變量上一文的最后有提起過,就是創(chuàng)建一個全局字典,然后將線程或者協(xié)程標識符作為,相應(yīng)線程或協(xié)程的局部數(shù)據(jù)作為。 在上篇我們看到了 ThreadLocal 變量的簡單使用,中篇對python中 ThreadLocal 的實現(xiàn)進行了分析,但故事還沒有結(jié)束。本篇我們一起來看下Werkzeug中ThreadLocal的設(shè)計。 Werkzeug...

    dadong 評論0 收藏0
  • 深入理解Python中的ThreadLocal變量(中)

    摘要:在深入理解中的變量上中我們看到的引入,使得可以很方便地在多線程環(huán)境中使用局部變量。特別需要注意的是,基類的并不會屏蔽派生類中的創(chuàng)建。到此,整個源碼核心部分已經(jīng)理解的差不多了,只剩下用來執(zhí)行清除工作。 在 深入理解Python中的ThreadLocal變量(上) 中我們看到 ThreadLocal 的引入,使得可以很方便地在多線程環(huán)境中使用局部變量。如此美妙的功能到底是怎樣實現(xiàn)的?如果你...

    DataPipeline 評論0 收藏0
  • 追蹤解析 ThreadLocal 源碼

    摘要:雖然類名中帶有字樣,但是實際上并不是接口的子類。是弱連接接口,這意味著如果僅有指向某一類,其任然有可能被回收掉。這里使用弱連接的意義,是為了防止業(yè)務(wù)代碼中置空對象,但是由于存在連接可達,所以仍然無法回收掉該對象的情況發(fā)生。 零 前期準備 0 FBI WARNING 文章異常啰嗦且繞彎。 1 版本 JDK 版本 : OpenJDK 11.0.1 IDE : idea 2018.3 2 T...

    wawor4827 評論0 收藏0
  • Java面試題必備知識之ThreadLocal

    摘要:方法,刪除當(dāng)前線程綁定的這個副本數(shù)字,這個值是的值,普通的是使用鏈表來處理沖突的,但是是使用線性探測法來處理沖突的,就是每次增加的步長,根據(jù)參考資料所說,選擇這個數(shù)字是為了讓沖突概率最小。 showImg(https://segmentfault.com/img/remote/1460000019828633); 老套路,先列舉下關(guān)于ThreadLocal常見的疑問,希望可以通過這篇學(xué)...

    Maxiye 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<