成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專(zhuān)欄INFORMATION COLUMN

【Java筆記】ThreadLocal的學(xué)習(xí)和理解

red_bricks / 777人閱讀

摘要:底層是一個(gè)的散列表可擴(kuò)容的數(shù)組,并采用開(kāi)放地址法來(lái)解決沖突。稍后討論方法每個(gè)對(duì)象都有一個(gè)值,每初始化一個(gè)對(duì)象,值就增加一個(gè)固定的大小。因此在使用的時(shí)候要手動(dòng)調(diào)用方法,防止內(nèi)存泄漏。

ThreadLocal定義

先看JDK關(guān)于ThreadLocal的類(lèi)注釋?zhuān)?/p>

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).

翻譯過(guò)來(lái)大概的意思為:ThreadLocal提供了線程內(nèi)部的局部變量;每個(gè)線程都有自己的,獨(dú)立的初始化變量副本;ThreadLocal實(shí)例通常是類(lèi)中的private static字段,該類(lèi)一般在線程狀態(tài)相關(guān)(或線程上下文)中使用。

Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal instance is accessible; after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other references to these copies exist).

翻譯過(guò)來(lái)大概的意思為:只要線程處于活動(dòng)狀態(tài)且ThreadLocal實(shí)例是可訪問(wèn)的狀態(tài)下,每個(gè)線程都持有對(duì)其線程局部變量副本的隱式引用;在線程消亡后,ThreadLocal實(shí)例s的所有副本都將進(jìn)行垃圾回收(除非存在對(duì)這些副本的其他引用)。

一些應(yīng)用場(chǎng)景

1、多線程下使用日志追蹤,如Logback或Log4j的MDC組件
2、在事務(wù)中,connection綁定到當(dāng)前線程來(lái)保證這個(gè)線程中的數(shù)據(jù)庫(kù)操作用的是同一個(gè)connection
3、dubbo的RpcContext的實(shí)現(xiàn):
4、現(xiàn)在的分布式trace系統(tǒng)中的traceId、spanId的傳遞等
5、web前臺(tái)的請(qǐng)求參數(shù),在同一線程內(nèi)多個(gè)方法之間隱式傳遞
。。。

ThreadLocal API的使用

一個(gè)簡(jiǎn)單的demo:

public class TraceContext {

    private static final ThreadLocal traceIdHolder = new ThreadLocal() {

        @Override
        protected String initialValue() {//①
            return UUID.randomUUID().toString().replaceAll("-", "");
        }
    };

    public static void setTraceId(String traceId) {
        traceIdHolder.set(traceId);//②
    }

    public static String getTraceId() {
        return traceIdHolder.get();//③
    }

    public static void removeTraceId() {
        traceIdHolder.remove();//④
    }
}

思考:ThreadLocal類(lèi)型的traceIdHolder一般被修飾為static、final、private,就是traceIdHolder在被使用的時(shí)候?yàn)閱卫豢勺儯ㄟ@不是常見(jiàn)的單例飽漢模式么)。如果traceIdHolder定義為多實(shí)例會(huì)怎么樣?

ThreadLocal實(shí)現(xiàn)解讀

以下以JDK1.8實(shí)現(xiàn)解讀

ThreadLocal的構(gòu)造函數(shù)為空:public ThreadLocal() {}

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

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

獲取當(dāng)前線程的ThreadLocalMap,有直接設(shè)置value、沒(méi)有新建
ThreadLocal的get方法:

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

從當(dāng)前線程的ThreadLocalMap中查找Entry,如果不必為null返回value,否則設(shè)置初值并返回setInitialValue()

ThreadLocal的remove()方法:

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

從當(dāng)前線程的ThreadLocalMap中刪除

ThreadLocal的setInitialValue()方法:

private T setInitialValue() {
    T value = initialValue();//未覆蓋就是null
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}

和set類(lèi)似

查看Thread的threadLocals的字段定義:ThreadLocal.ThreadLocalMap threadLocals = null;

查看ThreadLocal的內(nèi)部類(lèi)ThreadLocalMap的定義:

雖然ThreadLocalMap命名含有"Map",但和Map接口沒(méi)任何關(guān)系。ThreadLocalMap底層是一個(gè)的散列表(可擴(kuò)容的數(shù)組),并采用開(kāi)放地址法來(lái)解決hash沖突。

ThreadLocalMap.Entry定義:

static class Entry extends WeakReference> {
    /** The value associated with this ThreadLocal. */
    Object value;

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

這里先不管為啥使用WeakReference定義。稍后討論

ThreadLocalMap.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();

        if (k == key) {
            e.value = value;
            return;
        }

        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }

    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

每個(gè)ThreadLocal對(duì)象都有一個(gè)hash值threadLocalHashCode,每初始化一個(gè)ThreadLocal對(duì)象,hash值就增加一個(gè)固定的大小0x61c88647。
定義如下:

private final int threadLocalHashCode = nextHashCode();

/**
 * The next hash code to be given out. Updated atomically. Starts at
 * zero.
 */
private static AtomicInteger nextHashCode =
    new AtomicInteger();

/**
 * The difference between successively generated hash codes - turns
 * implicit sequential thread-local IDs into near-optimally spread
 * multiplicative hash values for power-of-two-sized tables.
 */
private static final int HASH_INCREMENT = 0x61c88647;

/**
 * Returns the next hash code.
 */
private static int nextHashCode() {
    return nextHashCode.getAndAdd(HASH_INCREMENT);
}

ThreadLocalMap.set流程總結(jié)如下:

根據(jù)當(dāng)前ThreadLocal的hashCode mod table.length,計(jì)算出應(yīng)插入的table位置下表i;

如果table[i]的Entry不為null,
①、判斷Entry.key == 當(dāng)前的ThreadLocal對(duì)象?相等覆蓋舊值 退出
②、如果Entry.key為null,將執(zhí)行刪除兩個(gè)null槽之間的所有的過(guò)期的stale的entry,并把當(dāng)前的位置i上初始化一個(gè)Entry對(duì)象,退出
③、繼續(xù)查找下一個(gè)位置i++

如果找到了一個(gè)位置k,table[k]為null,初始化一個(gè)Entry對(duì)象

ThreadLocalMap.getEntry方法:

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

private Entry getEntryAfterMiss(ThreadLocal key, int i, Entry e) {
    Entry[] tab = table;
    int len = tab.length;

    while (e != null) {
        ThreadLocal k = e.get();
        if (k == key)
            return e;
        if (k == null)
            expungeStaleEntry(i);
        else
            i = nextIndex(i, len);
        e = tab[i];
    }
    return null;
}

private int expungeStaleEntry(int staleSlot) {
    Entry[] tab = table;
    int len = tab.length;

    // expunge entry at staleSlot
    tab[staleSlot].value = null;
    tab[staleSlot] = null;
    size--;

    // Rehash until we encounter null
    Entry e;
    int i;
    for (i = nextIndex(staleSlot, len);
         (e = tab[i]) != null;
         i = nextIndex(i, len)) {
        ThreadLocal k = e.get();
        if (k == null) {
            e.value = null;
            tab[i] = null;
            size--;
        } else {
            int h = k.threadLocalHashCode & (len - 1);
            if (h != i) {
                tab[i] = null;

                // Unlike Knuth 6.4 Algorithm R, we must scan until
                // null because multiple entries could have been stale.
                while (tab[h] != null)
                    h = nextIndex(h, len);
                tab[h] = e;
            }
        }
    }
    return i;
}

ThreadLocalMap.getEntry流程總結(jié)如下:

根據(jù)當(dāng)前ThreadLocal的hashCode mod table.length,計(jì)算直接索引的位置i,如果e不為null并且key相同則返回e。

如果e為null,返回null

如果e不為空且key不相同,則查找下一個(gè)位置,繼續(xù)查找比較,直到e為null退出

在查找的過(guò)程中如果發(fā)現(xiàn)e不為空,且e的k為空的話,刪除當(dāng)前槽和下一個(gè)null槽之間的所有過(guò)期entry對(duì)象

ThreadLocalMap.remove方法:

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

ThreadLocalMap.remove流程總結(jié)如下:

計(jì)算直接索引的位置i,如果table[i]的entry e不為null,且key比較相等,則執(zhí)行刪除,把table[i]=null,table[i].value = null;然后刪除當(dāng)前槽和下一個(gè)null槽之間的所有過(guò)期entry對(duì)象

查找下一個(gè)位置,i++,直到table[i]的entry e為null退出

總結(jié)ThreadLocalMap:

散列采用開(kāi)放地址,線性探測(cè),在hash沖突較大的時(shí)候效率低下

ThreadLocalMap的set、get、remove操作中都帶有刪除過(guò)期元素的操作,類(lèi)似緩存的lazy淘汰

Thread、ThreadLoal、ThreadLocalMap關(guān)系圖:

整體的對(duì)象關(guān)系圖

關(guān)于ThreadLocal的內(nèi)存泄漏

以下分析轉(zhuǎn)自知乎作者winwill2012,鏈接:我覺(jué)得是這樣的

如上圖,ThreadLocalMap使用ThreadLocal的弱引用作為key,如果一個(gè)ThreadLocal沒(méi)有外部強(qiáng)引用引用他,那么系統(tǒng)gc的時(shí)候,這個(gè)ThreadLocal勢(shì)必會(huì)被回收,這樣一來(lái),ThreadLocalMap中就會(huì)出現(xiàn)key為null的Entry,就沒(méi)有辦法訪問(wèn)這些key為null的Entry的value,如果當(dāng)前線程再遲遲不結(jié)束的話,這些key為null的Entry的value就會(huì)一直存在一條強(qiáng)引用鏈:

ThreadRef -> Thread -> ThreaLocalMap -> Entry -> value

永遠(yuǎn)無(wú)法回收,造成內(nèi)存泄露。

因此在使用ThreadLocal的時(shí)候要手動(dòng)調(diào)用remove方法,防止內(nèi)存泄漏。

JDK建議將ThreadLocal變量定義成private static的,這樣的話ThreadLocal的生命周期就更長(zhǎng)(類(lèi)的靜態(tài)屬性引用的對(duì)象為GCRoots),由于一直存在ThreadLocal的強(qiáng)引用,所以ThreadLocal也就不會(huì)被回收,也就能保證任何時(shí)候都能根據(jù)ThreadLocal的弱引用訪問(wèn)到Entry的value值,然后remove它,防止內(nèi)存泄露。

我覺(jué)的JDK建議將ThreadLocal變量定義成private static的還有個(gè)可能原因是:?jiǎn)卫?,ThreadLocal對(duì)象是無(wú)狀態(tài)的,無(wú)含義的,聲明同一類(lèi)型的ThreadLocal對(duì)象多實(shí)例,浪費(fèi)ThreadLocalMap的存儲(chǔ)空間且對(duì)象更容易引起內(nèi)存泄漏。

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

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/71095.html

相關(guān)文章

  • Java經(jīng)典

    摘要:請(qǐng)注意,我們?cè)诹牧膯卧獪y(cè)試遇到問(wèn)題多思考多查閱多驗(yàn)證,方能有所得,再勤快點(diǎn)樂(lè)于分享,才能寫(xiě)出好文章。單元測(cè)試是指對(duì)軟件中的最小可測(cè)試單元進(jìn)行檢查和驗(yàn)證。 JAVA容器-自問(wèn)自答學(xué)HashMap 這次我和大家一起學(xué)習(xí)HashMap,HashMap我們?cè)诠ぷ髦薪?jīng)常會(huì)使用,而且面試中也很頻繁會(huì)問(wèn)到,因?yàn)樗锩嫣N(yùn)含著很多知識(shí)點(diǎn),可以很好的考察個(gè)人基礎(chǔ)。但一個(gè)這么重要的東西,我為什么沒(méi)有在一開(kāi)始...

    xcold 評(píng)論0 收藏0
  • Java面試題必備知識(shí)之ThreadLocal

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

    Maxiye 評(píng)論0 收藏0
  • 追蹤解析 ThreadLocal 源碼

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

    wawor4827 評(píng)論0 收藏0
  • 程序員筆記|如何編寫(xiě)高性能Java代碼

    摘要:常見(jiàn)標(biāo)高線程上下文切換頻繁線程太多鎖競(jìng)爭(zhēng)激烈標(biāo)高如果的占用很高,排查涉及到的程序,比如把改造成。抖動(dòng)問(wèn)題原因字節(jié)碼轉(zhuǎn)為機(jī)器碼需要占用時(shí)間片,大量的在執(zhí)行字節(jié)碼時(shí),導(dǎo)致長(zhǎng)期處于高位現(xiàn)象,占用率最高解決辦法保證編譯線程的占比。 一、并發(fā) Unable to create new native thread …… 問(wèn)題1:Java中創(chuàng)建一個(gè)線程消耗多少內(nèi)存? 每個(gè)線程有獨(dú)自的棧內(nèi)存,共享堆內(nèi)...

    ky0ncheng 評(píng)論0 收藏0
  • 06.Android之消息機(jī)制問(wèn)題

    摘要:通過(guò)向消息池發(fā)送各種消息事件通過(guò)處理相應(yīng)的消息事件。子線程往消息隊(duì)列發(fā)送消息,并且往管道文件寫(xiě)數(shù)據(jù),主線程即被喚醒,從管道文件讀取數(shù)據(jù),主線程被喚醒只是為了讀取消息,當(dāng)消息讀取完畢,再次睡眠。 目錄介紹 6.0.0.1 談?wù)勏C(jī)制Hander作用?有哪些要素?流程是怎樣的? 6.0.0.2 為什么一個(gè)線程只有一個(gè)Looper、只有一個(gè)MessageQueue,可以有多個(gè)Handle...

    Aomine 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<