最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java中的ThreadLocal與ThreadLocalMap詳解

 更新時(shí)間:2023年09月26日 10:11:46   作者:卑微小童  
這篇文章主要介紹了Java中的ThreadLocal與ThreadLocalMap詳解,ThreadLocal 是一個(gè)線程局部變量,其實(shí)的功用非常簡(jiǎn)單,就是為每一個(gè)使用該變量的線程都提供一個(gè)變量值的副本,是Java中一種較為特殊的線程綁定機(jī)制,需要的朋友可以參考下

ThreadLocal與ThreadLocalMap(jdk 1.8)

使用場(chǎng)景

  • 每個(gè)線程需要一個(gè)獨(dú)享的對(duì)象(通常是工具類)
  • 每個(gè)線程內(nèi)需要保存全局變量,可以在不同的地方直接獲取,避免參數(shù)傳遞的麻煩

作用

  • 讓某個(gè)需要用到的對(duì)象在線程間隔離(每個(gè)線程都有自己獨(dú)享的對(duì)象)
  • 任何方法中都可以輕松獲取其對(duì)象

好處

  • 可以達(dá)到線程安全
  • 不需要加鎖,提高效率
  • 高效利用內(nèi)存,相比于每個(gè)任務(wù)都新建一個(gè)對(duì)象,用ThreadLocal可以節(jié)省內(nèi)存和開銷
  • 免去傳遞參數(shù)的繁瑣,降低了程序耦合度

主要方法

1)initialValue()

該方法會(huì)返回當(dāng)前線程對(duì)應(yīng)的初始值,采用了懶加載機(jī)制,當(dāng)?shù)谝淮蝕et的時(shí)候才會(huì)觸發(fā),當(dāng)線程第一次使用get方法的時(shí)候才會(huì)觸發(fā)。除非線程先前調(diào)用了set方法,在這種情況下,不會(huì)再調(diào)用InitValue方法

2)set(T value)

未當(dāng)前線程設(shè)置一個(gè)新的值

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

T get()

public T get() {
    Thread t = Thread.currentThread();//獲取當(dāng)前線程
    ThreadLocalMap map = getMap(t);//從當(dāng)前線程中獲取ThreadLocalMap
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);//獲取Entry
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;//返回對(duì)象
        }
    }
    return setInitialValue();//如果第一次調(diào)用get,ThreadLocalMap未空或者在ThreadLocalMap中還未存儲(chǔ)對(duì)象,則進(jìn)行初始化并返回存儲(chǔ)對(duì)象
  }

remove()

//移除線程所存儲(chǔ)對(duì)象
public void remove() {
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
        m.remove(this);
}

原理

Thread類中又這樣一個(gè)ThreadLocalMap 類型成員變量threadLocals

ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocalMap 是ThreadLocal的內(nèi)部類,其結(jié)構(gòu)如HashMap很相似,在其內(nèi)部還有個(gè)Entry,保存ThreadLocal和其保存的對(duì)象。其默認(rèn)容量也為16,負(fù)載因子未2/3,并且不存在next指針,哈希沖突后采用的延后策略。具體請(qǐng)看最后問(wèn)題欄

static class ThreadLocalMap {
    static class Entry extends WeakReference<ThreadLocal<?>> {
        Object value;
        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
    private static final int INITIAL_CAPACITY = 16;
    private Entry[] table;
    private int size = 0;
    private int threshold; //閾值
    private void setThreshold(int len) {
            threshold = len * 2 / 3;  //負(fù)載因子是2/3,
    }
    //......省略............
}

在這里插入圖片描述

總的來(lái)說(shuō),在Thead中維護(hù)了一個(gè)Map,在Map中存儲(chǔ)了ThreadLocal和其綁定的對(duì)象

每次獲取對(duì)象都會(huì)從當(dāng)前線程中獲取map并將ThreadLocal傳入從而獲得對(duì)象

內(nèi)存泄露

ThreadLocal被用作TheadLocalMap的弱引用key,這種設(shè)計(jì)也是ThreadLocal被討論內(nèi)存泄露的熱點(diǎn)問(wèn)題,因此有必要了解一下什么是弱引用。

弱引用

弱引用是用來(lái)描述非必須的對(duì)象的,但它的強(qiáng)度比軟引用更弱,被弱引用關(guān)聯(lián)的對(duì)象只能生存到下一次GC發(fā)生之前,也就是說(shuō)下一次GC就會(huì)被回收。JDK1.2之后,提供了WeakReference來(lái)實(shí)現(xiàn)弱引用。

? 由于ThreadLocalMap是以弱引用的方式引用著ThreadLocal,換句話說(shuō),就是ThreadLocal是被ThreadLocalMap以弱引用的方式關(guān)聯(lián)著,因此如果ThreadLocal沒(méi)有被ThreadLocalMap以外的對(duì)象引用,則在下一次GC的時(shí)候,ThreadLocal實(shí)例就會(huì)被回收,那么此時(shí)ThreadLocalMap里的一組KV的K就是null了,因此在沒(méi)有額外操作的情況下,此處的V便不會(huì)被外部訪問(wèn)到,而且只要Thread實(shí)例一直存在,Thread實(shí)例就強(qiáng)引用著ThreadLocalMap,因此ThreadLocalMap就不會(huì)被回收,那么這里K為null的V就一直占用著內(nèi)存。

綜上,發(fā)生內(nèi)存泄露的條件是

  • ThreadLocal實(shí)例沒(méi)有被外部強(qiáng)引用,比如我們假設(shè)在提交到線程池的task中實(shí)例化的ThreadLocal對(duì)象,當(dāng)task結(jié)束時(shí),ThreadLocal的強(qiáng)引用也就結(jié)束了
  • ThreadLocal實(shí)例被回收,但是在ThreadLocalMap中的V沒(méi)有被任何清理機(jī)制有效清理
  • 當(dāng)前Thread實(shí)例一直存在,則會(huì)一直強(qiáng)引用著ThreadLocalMap,也就是說(shuō)ThreadLocalMap也不會(huì)被GC

示例

class Test{
    byte data[]=new byte[1024*1024*10];
    @Override
    protected void finalize() throws Throwable {
        System.out.println("destroy");
    }
}
public class ThreadLocalDemo {
    public ThreadLocal<Test> t = new ThreadLocal<>();
    public static void main(String[] args) {
        ThreadLocalDemo threadLocalDemo = new ThreadLocalDemo();
        Test test = new Test();
        threadLocalDemo.t.set(test);
        test = null;
        //threadLocalDemo.t.remove();
        threadLocalDemo = null;
        System.out.println("start gc");
        System.gc();
        try {
            Thread.sleep(1000L);
        }catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("end");
    }
}

輸出
/*
start gc
end
*/
//當(dāng)threadLocalDemo.t.remove();不被注釋
/*
輸出:
start gc
destroy
end
*/

當(dāng)不在持有ThreadLocalDemo對(duì)象,因?yàn)閠hread中ThreadLoaclMap中保存有ThreadLocal的引用 ,如果ThreadLocal不是弱引用的話,ThreadLocal是不可能被gc的。而如果ThreadLocal與ThreadLocalMap之間是弱引用,如果除Thread外沒(méi)有任何對(duì)象可以獲得ThreadLocal,則ThreadLocal是可以為回收的

? 當(dāng)然,其仍然仍然存在一定的內(nèi)存泄露,即value與TreadLcoalMap之間存在引用,當(dāng)ThreadLocal被gc時(shí)value是無(wú)法被gc的,但是在ThreadLocalMap內(nèi)部也存在一些機(jī)制,當(dāng)map擴(kuò)容或者發(fā)生hash沖突的時(shí)候會(huì)判斷key鍵是否為null(即判斷ThreadLocal對(duì)象是否被回收),如果是null,則會(huì)將value值同樣設(shè)為Null.從而幫助value gc

ThreadLocal為什么經(jīng)常設(shè)置為static

public class ThreadLocalDemo2 {
    public ThreadLocal<Test> t = new ThreadLocal<>();
	//public static ThreadLocal<Test> t = new ThreadLocal<>();
    public static void main(String[] args) {
        ThreadLocalDemo2 threadLocalDemo = new ThreadLocalDemo2();
        Test test = new Test();
        test.name = "xxxx";
        threadLocalDemo.t.set(test);
        ThreadLocalDemo2 threadLocalDemo2 = new ThreadLocalDemo2();
        Test test2 = new Test();
        test2.name = "yyyyy";
        threadLocalDemo2.t.set(test2);
        System.out.println(threadLocalDemo.t.get().name);
        System.out.println(threadLocalDemo2.t.get().name);
    }
}

/*
輸出:
xxxx
yyyyy

static 修飾 ThreadLocal
輸出:
yyyyy
yyyyy
*/

static修飾ThreadLocal后,單個(gè)線程無(wú)論創(chuàng)建多個(gè)對(duì)象,其ThreadLocal示例僅僅只有一個(gè)。

如果變量ThreadLocal是非static的就會(huì)造成每次生成實(shí)例都要生成不同的ThreadLocal對(duì)象,雖然這樣程序不會(huì)有什么異常,但是會(huì)浪費(fèi)內(nèi)存資源,甚至?xí)斐蓛?nèi)存泄漏.。

建議

  • 通過(guò)前面幾節(jié)的分析,我們基本弄清楚了ThreadLocal相關(guān)設(shè)計(jì)和內(nèi)存模型,對(duì)于是否會(huì)發(fā)生內(nèi)存泄露做了分析,下面總結(jié)下幾點(diǎn)建議:
  • 當(dāng)需要存儲(chǔ)線程私有變量的時(shí)候,可以考慮使用ThreadLocal來(lái)實(shí)現(xiàn)
  • 當(dāng)需要實(shí)現(xiàn)線程安全的變量時(shí),可以考慮使用ThreadLocal來(lái)實(shí)現(xiàn)
  • 當(dāng)需要減少線程資源競(jìng)爭(zhēng)的時(shí)候,可以考慮使用ThreadLocal來(lái)實(shí)現(xiàn)
  • 注意Thread實(shí)例和ThreadLocal實(shí)例的生存周期,因?yàn)樗麄冎苯雨P(guān)聯(lián)著存儲(chǔ)數(shù)據(jù)的生命周期
  • 如果頻繁的在線程中new ThreadLocal對(duì)象,在使用結(jié)束時(shí),最好調(diào)用ThreadLocal.remove來(lái)釋放其value的引用,避免在ThreadLocal被回收時(shí)value無(wú)法被訪問(wèn)卻又占用著內(nèi)存

問(wèn)題:

為什么ThreadLocalMap不用HashMap而是自己寫了個(gè)Map

  • 自定義Map限定了鍵值未ThreadLocal類型
  • 其Entry對(duì)象繼承了弱引用類,用來(lái)存儲(chǔ)鍵值,從而不影響對(duì)象被回收,而HashMap中Key是強(qiáng)引用
  • ThreadLocalMap在寫數(shù)據(jù)和查數(shù)據(jù)的過(guò)程中有一個(gè)清理過(guò)期數(shù)據(jù)的功能,能夠?qū)l(fā)現(xiàn)的過(guò)期數(shù)據(jù)清理到,從某種意義上也是解決了內(nèi)存泄漏問(wèn)題。當(dāng)然不是完全解決

ThreadLocalMap達(dá)到擴(kuò)容的閾值時(shí)會(huì)真正的擴(kuò)容嗎?

不會(huì),達(dá)到閾值之后,進(jìn)行一個(gè)散列表的掃描清楚過(guò)期的數(shù)據(jù),如果清理完之后,數(shù)據(jù)量仍然達(dá)到其閾值的75%,才進(jìn)行擴(kuò)容

擴(kuò)容源碼:

private void rehash() {
    expungeStaleEntries();//清理
    if (size >= threshold - threshold / 4)//數(shù)據(jù)量仍然達(dá)到其閾值的75%,才進(jìn)行擴(kuò)容
        resize();
}
private void resize() {
    Entry[] oldTab = table;
    int oldLen = oldTab.length;
    int newLen = oldLen * 2;
    Entry[] newTab = new Entry[newLen];//新建一個(gè)數(shù)組
    int count = 0;
    for (int j = 0; j < oldLen; ++j) {//遍歷
        Entry e = oldTab[j];
        if (e != null) {
            ThreadLocal<?> k = e.get();
            if (k == null) {
                e.value = null; //將value設(shè)為null從而幫助GC
            } else {
                //重新進(jìn)行hash
                int h = k.threadLocalHashCode & (newLen - 1);
                while (newTab[h] != null)
                    h = nextIndex(h, newLen);//采用的時(shí)自定義hash算法
                newTab[h] = e;
                count++;
            }
        }
    }
    setThreshold(newLen);//計(jì)算新的閾值
    size = count;
    table = newTab;
}

ThreadLocalMap獲取Entry的流程

private Entry getEntry(ThreadLocal<?> key) {
    int i = key.threadLocalHashCode & (table.length - 1);//hash運(yùn)算計(jì)算出位置
    Entry e = table[i];
    if (e != null && e.get() == key)//未發(fā)生過(guò)Hash沖突
        return e;
    else//發(fā)生過(guò)沖突
        return getEntryAfterMiss(key, i, e);//進(jìn)行下一個(gè)位置的判斷
}
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)//如果為空,則說(shuō)明此位置被GC了,為過(guò)期數(shù)據(jù)
            expungeStaleEntry(i);//為了防止內(nèi)存泄漏,觸發(fā)一個(gè)“探測(cè)式”過(guò)期數(shù)據(jù)回收邏輯
        else
            i = nextIndex(i, len);//計(jì)算下一個(gè)位置
        e = tab[i];
    }
    return null;
}
//“探測(cè)式”過(guò)期數(shù)據(jù)回收邏輯
private int expungeStaleEntry(int staleSlot) {
    Entry[] tab = table;
    int len = tab.length;
    tab[staleSlot].value = null;//將value設(shè)為空,幫助GC
    tab[staleSlot] = null;
    size--;
    Entry e;
    int i;
    for (i = nextIndex(staleSlot, len);//根據(jù)hash和尋址算法遍歷所有與當(dāng)前hash相同的槽點(diǎn)
         (e = tab[i]) != null;
         i = nextIndex(i, len)) {
        ThreadLocal<?> k = e.get();
        if (k == null) {//幫助GC
            e.value = null;
            tab[i] = null;
            size--;
        } else {
            //如果key不為空,則重新進(jìn)行hash,將其移動(dòng)到一個(gè)更靠近其hash位置的槽點(diǎn)(提高下次get的效率)
            int h = k.threadLocalHashCode & (len - 1);
            if (h != i) {
                tab[i] = null;
                while (tab[h] != null)
                    h = nextIndex(h, len);
                tab[h] = e;
            }
        }
    }
    return i;
}

ThreadLocalMap中set的具體流程

private void set(ThreadLocal<?> key, Object value) {
	//尋址
    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);
	//遍歷可能的slot
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();
		//如果key相同,則替換
        if (k == key) {
            e.value = value;
            return;
        }
		//如果k為空,則進(jìn)行取代算法
        if (k == null) {
            //大體就是遍歷可能的槽點(diǎn),直到碰到key值相同的,則將其移動(dòng)到距離真實(shí)hash位置最近的點(diǎn),如果沒(méi)有,則再最有好的位置new一個(gè)新的Entry
            replaceStaleEntry(key, value, i);
            return;
        }
    }
    tab[i] = new Entry(key, value);
    int sz = ++size;//判斷是否達(dá)到擴(kuò)容條件
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

到此這篇關(guān)于Java中的ThreadLocal與ThreadLocalMap詳解的文章就介紹到這了,更多相關(guān)ThreadLocal與ThreadLocalMap內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 基于Java反射技術(shù)實(shí)現(xiàn)簡(jiǎn)單IOC容器

    基于Java反射技術(shù)實(shí)現(xiàn)簡(jiǎn)單IOC容器

    這篇文章主要介紹了基于Java反射技術(shù)實(shí)現(xiàn)簡(jiǎn)單IOC容器,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-07-07
  • Java?Spring的核心與設(shè)計(jì)思想你知道嗎

    Java?Spring的核心與設(shè)計(jì)思想你知道嗎

    這篇文章主要為大家詳細(xì)介紹了Java?Spring的核心與設(shè)計(jì)思想,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助
    2022-03-03
  • Java?數(shù)據(jù)庫(kù)連接池Druid?的介紹

    Java?數(shù)據(jù)庫(kù)連接池Druid?的介紹

    這篇文章主要給大家分享的是?Java?數(shù)據(jù)庫(kù)連接池Druid?的介紹,Druid是一個(gè)JDBC組件,它包括三部分:?DruidDriver?代理Driver,能夠提供基于Filter-Chain模式的插件體系。?DruidDataSource?高效可管理的數(shù)據(jù)庫(kù)連接池,下面來(lái)看看文中的詳細(xì)內(nèi)容,需要的朋友也可以參考一下
    2021-11-11
  • springboot整合mybatis中的問(wèn)題及出現(xiàn)的一些問(wèn)題小結(jié)

    springboot整合mybatis中的問(wèn)題及出現(xiàn)的一些問(wèn)題小結(jié)

    這篇文章主要介紹了springboot整合mybatis中的問(wèn)題及出現(xiàn)的一些問(wèn)題小結(jié),本文給出了解決方案,需要的朋友可以參考下
    2018-11-11
  • 如何通過(guò)maven最快搭建Springboot項(xiàng)目

    如何通過(guò)maven最快搭建Springboot項(xiàng)目

    因?yàn)镾pringboot項(xiàng)目的啟動(dòng)依賴是通過(guò)pom.xml文件實(shí)現(xiàn)的,所以我們直接創(chuàng)建項(xiàng)目后,調(diào)整pom.xml即可快速開發(fā),這篇文章給大家介紹如何通過(guò)maven最快搭建Springboot項(xiàng)目,感興趣的朋友一起看看吧
    2025-05-05
  • IDEA中的yml文件與properties互相轉(zhuǎn)換

    IDEA中的yml文件與properties互相轉(zhuǎn)換

    這篇文章主要介紹了IDEA中的yml文件與properties互相轉(zhuǎn)換方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-10-10
  • 快速了解JAVA垃圾回收機(jī)制

    快速了解JAVA垃圾回收機(jī)制

    這篇文章主要介紹了有關(guān)Java垃圾回收機(jī)制的知識(shí),文中實(shí)例簡(jiǎn)單易懂,方便大家更好的學(xué)習(xí),有興趣的朋友可以了解下
    2020-06-06
  • nacos單機(jī)本地配置文件存儲(chǔ)位置方式

    nacos單機(jī)本地配置文件存儲(chǔ)位置方式

    這篇文章主要介紹了nacos單機(jī)本地配置文件存儲(chǔ)位置方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-11-11
  • Jdbc連接數(shù)據(jù)庫(kù)基本步驟詳解

    Jdbc連接數(shù)據(jù)庫(kù)基本步驟詳解

    這篇文章主要為大家詳細(xì)介紹了Jdbc連接數(shù)據(jù)庫(kù)的基本步驟,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-10-10
  • java獲取反射機(jī)制的3種方法總結(jié)

    java獲取反射機(jī)制的3種方法總結(jié)

    這篇文章主要給大家介紹了關(guān)于java獲取反射機(jī)制的3種方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用java具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-06-06

最新評(píng)論

拜泉县| 遵义市| 马龙县| 科尔| 离岛区| 南阳市| 佛坪县| 天门市| 娱乐| 韶山市| 水富县| 平泉县| 江阴市| 涞水县| 彩票| 韶关市| 耒阳市| 本溪市| 孟连| 重庆市| 格尔木市| 二连浩特市| 奈曼旗| 金塔县| 永州市| 香河县| 龙游县| 彭阳县| 定兴县| 南安市| 浑源县| 乌拉特前旗| 阿尔山市| 龙州县| 筠连县| 靖边县| 都匀市| 中西区| 武威市| 廊坊市| 吐鲁番市|