0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

如何使用ThreadLocal來(lái)避免內(nèi)存泄漏

5jek_harmonyos ? 來(lái)源:CSDN博客 ? 作者:pony-zi ? 2021-08-20 09:29 ? 次閱讀

本次給大家介紹重要的工具ThreadLocal。講解內(nèi)容如下,同時(shí)介紹什么場(chǎng)景下發(fā)生內(nèi)存泄漏,如何復(fù)現(xiàn)內(nèi)存泄漏,如何正確使用它來(lái)避免內(nèi)存泄漏。

ThreadLocal是什么?有哪些用途?

ThreadLocal如何使用

ThreadLocal原理

ThreadLocal使用有哪些坑及注意事項(xiàng)

Part1ThreadLocal是什么?有哪些用途?

首先介紹Thread類中屬性threadLocals:

/* ThreadLocal values pertaining to this thread.

This map is maintained * by the ThreadLocal class. */

ThreadLocal.ThreadLocalMap threadLocals = null;

我們發(fā)現(xiàn)Thread并沒(méi)有提供成員變量threadLocals的設(shè)置與訪問(wèn)的方法,那么每個(gè)線程的實(shí)例threadLocals參數(shù)我們?nèi)绾尾僮髂兀窟@時(shí)我們的主角:ThreadLocal就登場(chǎng)了。

所以有那么一句總結(jié):

ThreadLocal是線程Thread中屬性threadLocals的管理者。

也就是說(shuō)我們對(duì)于ThreadLocal的get, set,remove的操作結(jié)果都是針對(duì)當(dāng)前線程Thread實(shí)例的threadLocals存,取,刪除操作。類似于一個(gè)開發(fā)者的任務(wù),產(chǎn)品經(jīng)理左右不了,產(chǎn)品經(jīng)理只能通過(guò)技術(shù)leader來(lái)給開發(fā)者分配任務(wù)。

下面再舉個(gè)栗子,進(jìn)一步說(shuō)明他們之間的關(guān)系:

1. 每個(gè)人都一張銀行卡

每個(gè)人每張卡都有一定的余額。

每個(gè)人獲取銀行卡余額都必須通過(guò)該銀行的管理系統(tǒng)。

每個(gè)人都只能獲取自己卡持有的余額信息,他人的不可訪問(wèn)。

映射到我們要說(shuō)的ThreadLocal

card類似于Thread

card余額屬性,卡號(hào)屬性等類似于Treadlocal內(nèi)部屬性集合threadLocals

cardManager類似于ThreadLocal管理類

那ThreadLocal有哪些應(yīng)用場(chǎng)景呢?

其實(shí)我們無(wú)意間已經(jīng)時(shí)時(shí)刻刻在使用ThreadLocal提供的便利,如果說(shuō)多數(shù)據(jù)源的切換你比較陌生,那么spring提供的聲明式事務(wù)就再熟悉不過(guò)了,我們?cè)谘邪l(fā)過(guò)程中無(wú)時(shí)無(wú)刻不在使用,而spring聲明式事務(wù)的重要實(shí)現(xiàn)基礎(chǔ)就是ThreadLocal,只不過(guò)大家沒(méi)有去深入研究spring聲明式事務(wù)的實(shí)現(xiàn)機(jī)制。后面有機(jī)會(huì)我會(huì)給大家介紹spring聲明式事務(wù)的原理及實(shí)現(xiàn)機(jī)制。

原來(lái)ThreadLocal這么強(qiáng)大,但應(yīng)用開發(fā)者使用較少,同時(shí)有些研發(fā)人員對(duì)于ThreadLocal內(nèi)存泄漏,等潛在問(wèn)題,不敢試用,恐怕這是對(duì)于ThreadLocal最大的誤解,后面我們將會(huì)仔細(xì)分析,只要按照正確使用方式,就沒(méi)什么問(wèn)題。如果ThreadLocal存在問(wèn)題,豈不是spring聲明式事務(wù)是我們程序最大的潛在危險(xiǎn)嗎?

Part2ThreadLocal如何使用

為了更直觀的體會(huì)ThreadLocal的使用我們假設(shè)如下場(chǎng)景

我們給每個(gè)線程生成一個(gè)ID。

一旦設(shè)置,線程生命周期內(nèi)不可變化。

容器活動(dòng)期間不可以生成重復(fù)的ID

我們創(chuàng)建一個(gè)ThreadLocal管理類:

2981b0cc-013e-11ec-9bcf-12bb97331649.png

測(cè)試程序如下:我們同一個(gè)線程不斷get,測(cè)試id是否變化,同時(shí)測(cè)試完成后我們就將其釋放掉。

29adbec4-013e-11ec-9bcf-12bb97331649.png

在主程序中我們開啟多個(gè)線程測(cè)試不通線程之間是否會(huì)影響

29cdd862-013e-11ec-9bcf-12bb97331649.jpg

不出意外我們的結(jié)果為:

2a1f53c2-013e-11ec-9bcf-12bb97331649.jpg

結(jié)果:確實(shí)是不同線程間id不同,相同線程id相同。

Part3ThreadLocal原理

①ThreadLocal類結(jié)構(gòu)及方法解析:

2a5b56b0-013e-11ec-9bcf-12bb97331649.png

上圖可知:ThreadLocal三個(gè)方法get, set , remove以及內(nèi)部類ThreadLocalMap

②ThreadLocal及Thread之間的關(guān)系:

2a8604a0-013e-11ec-9bcf-12bb97331649.png

從這張圖我們可以直觀的看到Thread中屬性threadLocals,作為一個(gè)特殊的Map,它的key值就是我們ThreadLocal實(shí)例,而value值這是我們?cè)O(shè)置的值。

③ThreadLocal的操作過(guò)程:

我們以get方法為例:

2aa836e2-013e-11ec-9bcf-12bb97331649.png

其中g(shù)etMap(t)返回的就上當(dāng)前線程的threadlocals,如下圖,然后根據(jù)當(dāng)前ThreadLocal實(shí)例對(duì)象作為key獲取ThreadLocalMap中的value,如果首次進(jìn)來(lái)這調(diào)用setInitialValue()

2ac868d6-013e-11ec-9bcf-12bb97331649.png

2af774aa-013e-11ec-9bcf-12bb97331649.png

set的過(guò)程也類似:

2b14afa2-013e-11ec-9bcf-12bb97331649.png

注意:ThreadLocal中可以直接t.threadLocals是因?yàn)門hread與ThreadLocal在同一個(gè)包下,同樣Thread可以直接訪問(wèn)ThreadLocal.ThreadLocalMap threadLocals = null;來(lái)進(jìn)行聲明屬性。另外,歡迎關(guān)注公眾號(hào)Java筆記蝦,后臺(tái)回復(fù)“后端面試”,送你一份面試題寶典!

Part4ThreadLocal使用有哪些坑及注意事項(xiàng)

我經(jīng)常在網(wǎng)上看到駭人聽聞的標(biāo)題,ThreadLocal導(dǎo)致內(nèi)存泄漏,這通常讓一些剛開始對(duì)ThreadLocal理解不透徹的開發(fā)者,不敢貿(mào)然使用。越不用,越陌生。這樣就讓我們錯(cuò)失了更好的實(shí)現(xiàn)方案,所以敢于引入新技術(shù),敢于踩坑,才能不斷進(jìn)步。

我們來(lái)看下為什么說(shuō)ThreadLocal會(huì)引起內(nèi)存泄漏,什么場(chǎng)景下會(huì)導(dǎo)致內(nèi)存泄漏?

先回顧下什么叫內(nèi)存泄漏,對(duì)應(yīng)的什么叫內(nèi)存溢出

Memory overflow:內(nèi)存溢出,沒(méi)有足夠的內(nèi)存提供申請(qǐng)者使用。

Memory leak:內(nèi)存泄漏,程序申請(qǐng)內(nèi)存后,無(wú)法釋放已申請(qǐng)的內(nèi)存空間,內(nèi)存泄漏的堆積終將導(dǎo)致內(nèi)存溢出。

顯然是TreadLocal在不規(guī)范使用的情況下導(dǎo)致了內(nèi)存沒(méi)有釋放。

2b9a3816-013e-11ec-9bcf-12bb97331649.png

紅框里我們看到了一個(gè)特殊的類WeakReference,同樣這個(gè)類,應(yīng)用開發(fā)者也同樣很少使用,這里簡(jiǎn)單介紹下吧

2bd74076-013e-11ec-9bcf-12bb97331649.jpg

既然WeakReference在下一次gc即將被回收,那么我們的程序?yàn)槭裁礇](méi)有出問(wèn)題呢?

①所以我們測(cè)試下弱引用的回收機(jī)制:

2beedec0-013e-11ec-9bcf-12bb97331649.png

這一種存在強(qiáng)引用不會(huì)被回收。

2c12f152-013e-11ec-9bcf-12bb97331649.png

這里沒(méi)有強(qiáng)引用將會(huì)被回收。

上面演示了弱引用的回收情況,下面我們看下ThreadLocal的弱引用回收情況。

②ThreadLocal的弱引用回收情況

2c36b47a-013e-11ec-9bcf-12bb97331649.jpg

如上圖所示,我們?cè)谧鳛閗ey的ThreadLocal對(duì)象沒(méi)有外部強(qiáng)引用,下一次gc必將產(chǎn)生key值為null的數(shù)據(jù),若線程沒(méi)有及時(shí)結(jié)束必然出現(xiàn),一條強(qiáng)引用鏈Threadref–》Thread–》ThreadLocalMap–》Entry,所以這將導(dǎo)致內(nèi)存泄漏。

下面我們模擬復(fù)現(xiàn)ThreadLocal導(dǎo)致內(nèi)存泄漏:

1.為了效果更佳明顯我們將我們的treadlocals的存儲(chǔ)值value設(shè)置為1萬(wàn)字符串的列表:

class ThreadLocalMemory {

// Thread local variable containing each thread‘s ID

public ThreadLocal《List《Object》》 threadId = new ThreadLocal《List《Object》》() {

@Override

protected List《Object》 initialValue() {

List《Object》 list = new ArrayList《Object》();

for (int i = 0; i 《 10000; i++) {

list.add(String.valueOf(i));

}

return list;

}

};

// Returns the current thread’s unique ID, assigning it if necessary

public List《Object》 get() {

return threadId.get();

}

// remove currentid

public void remove() {

threadId.remove();

}

}

測(cè)試代碼如下:

public static void main(String[] args)

throws InterruptedException {

// 為了復(fù)現(xiàn)key被回收的場(chǎng)景,我們使用臨時(shí)變量

ThreadLocalMemory memeory = new ThreadLocalMemory();

// 調(diào)用

incrementSameThreadId(memeory);

System.out.println(“GC前:key:” + memeory.threadId);

System.out.println(“GC前:value-size:” + refelectThreadLocals(Thread.currentThread()));

// 設(shè)置為null,調(diào)用gc并不一定觸發(fā)垃圾回收,但是可以通過(guò)java提供的一些工具進(jìn)行手工觸發(fā)gc回收。

memeory.threadId = null;

System.gc();

System.out.println(“GC后:key:” + memeory.threadId);

System.out.println(“GC后:value-size:” + refelectThreadLocals(Thread.currentThread()));

// 模擬線程一直運(yùn)行

while (true) {

}

}

此時(shí)我們?nèi)绾沃纼?nèi)存中存在memory leak呢?

我們可以借助jdk提供的一些命令dump當(dāng)前堆內(nèi)存,命令如下:

jmap -dump:live,format=b,file=heap.bin 《pid》

然后我們借助MAT可視化分析工具,來(lái)查看對(duì)內(nèi)存,分析對(duì)象實(shí)例的存活狀態(tài):

首先打開我們工具提示我們的內(nèi)存泄漏分析:

這里我們可以確定的是ThreadLocalMap實(shí)例的Entry.value是沒(méi)有被回收的。

最后我們要確定Entry.key是否還在?打開Dominator Tree,搜索我們的ThreadLocalMemory,發(fā)現(xiàn)并沒(méi)有存活的實(shí)例。

以上我們復(fù)現(xiàn)了ThreadLocal不正當(dāng)使用,引起的內(nèi)存泄漏。

文中源碼:

https://github.com/z-one/basictest/tree/master/src/main/java/com/spring/test/threadlocal

所以我們總結(jié)了使用ThreadLocal時(shí)會(huì)發(fā)生內(nèi)存泄漏的前提條件:

ThreadLocal引用被設(shè)置為null,且后面沒(méi)有set,get,remove操作。

線程一直運(yùn)行,不停止。(線程池)

觸發(fā)了垃圾回收。(Minor GC或Full GC)

我們看到ThreadLocal出現(xiàn)內(nèi)存泄漏條件還是很苛刻的,所以我們只要破壞其中一個(gè)條件就可以避免內(nèi)存泄漏,單但為了更好的避免這種情況的發(fā)生我們使用ThreadLocal時(shí)遵守以下兩個(gè)小原則:

①ThreadLocal申明為private static final。

Private與final 盡可能不讓他人修改變更引用,

Static 表示為類屬性,只有在程序結(jié)束才會(huì)被回收。

②ThreadLocal使用后務(wù)必調(diào)用remove方法。

最簡(jiǎn)單有效的方法是使用后將其移除。

責(zé)任編輯:haq

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 內(nèi)存
    +關(guān)注

    關(guān)注

    8

    文章

    2966

    瀏覽量

    73812
  • 內(nèi)存泄露
    +關(guān)注

    關(guān)注

    0

    文章

    6

    瀏覽量

    1977

原文標(biāo)題:ThreadLocal理解, 運(yùn)用以及內(nèi)存泄漏的處理方案

文章出處:【微信號(hào):harmonyos_developer,微信公眾號(hào):harmonyos_developer】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    ThreadLocal實(shí)例應(yīng)用

    ThreadLocal相信大家都用過(guò),但你知道他的原理嗎,今天了不起帶大家學(xué)習(xí)ThreadLocal。 ThreadLocal是什么 在多線程編程中,經(jīng)常會(huì)遇到需要在不同線程中共享數(shù)據(jù)的情況
    的頭像 發(fā)表于 09-30 10:19 ?631次閱讀
    <b class='flag-5'>ThreadLocal</b>實(shí)例應(yīng)用

    ThreadLocal的定義、用法及優(yōu)點(diǎn)

    ThreadLocal 簡(jiǎn)介 ThreadLocal是Java中一個(gè)非常重要的線程技術(shù)。它可以讓每個(gè)線程都擁有自己的變量副本,避免了線程間的競(jìng)爭(zhēng)和數(shù)據(jù)泄露問(wèn)題。在本文中,我們將詳細(xì)介紹
    的頭像 發(fā)表于 09-30 10:14 ?974次閱讀
    <b class='flag-5'>ThreadLocal</b>的定義、用法及優(yōu)點(diǎn)

    內(nèi)存泄漏定位該如何去實(shí)現(xiàn)呢

    。對(duì)于內(nèi)存泄漏的情況,如果一開始不做預(yù)防,定位內(nèi)存泄漏就會(huì)相當(dāng)繁瑣,定位也會(huì)很長(zhǎng),非常的耗時(shí)、耗力。這里可通過(guò)malloc、free的第二次封裝來(lái)
    發(fā)表于 12-17 07:24

    ThreadLocal發(fā)生內(nèi)存泄漏的原因

    ,就可能會(huì)導(dǎo)致內(nèi)存泄漏。下面,我們將圍繞三個(gè)方面來(lái)分析 ThreadLocal 內(nèi)存泄漏的問(wèn)題
    的頭像 發(fā)表于 05-05 16:23 ?3650次閱讀

    內(nèi)存泄漏的特點(diǎn)和類型

    在計(jì)算機(jī)科學(xué)中,內(nèi)存泄漏(memory leak)指由于疏忽或錯(cuò)誤使程序未能釋放而造成不能再使用的內(nèi)存的情況。內(nèi)存泄漏并非指
    的頭像 發(fā)表于 06-20 10:58 ?2777次閱讀

    內(nèi)存泄漏問(wèn)題原理及檢視方法

    可能不少開發(fā)者都遇到過(guò)內(nèi)存泄漏導(dǎo)致的網(wǎng)上問(wèn)題,具體表現(xiàn)為單板在現(xiàn)網(wǎng)運(yùn)行數(shù)月以后,因?yàn)?b class='flag-5'>內(nèi)存耗盡而導(dǎo)致單板復(fù)位現(xiàn)象。一方面,內(nèi)存泄漏問(wèn)題屬于比較
    的頭像 發(fā)表于 10-10 10:42 ?2499次閱讀

    如何避免內(nèi)存泄漏的方法和原則

    本文向讀者介紹了如何避免內(nèi)存泄漏的方法和原則,在細(xì)節(jié)和大體方向上均給出一些可行性方案。讀者可以嘗試文中提出的方法,改進(jìn)自己的代碼,大大減少內(nèi)存泄漏
    的頭像 發(fā)表于 10-21 14:30 ?5856次閱讀
    如何<b class='flag-5'>避免</b><b class='flag-5'>內(nèi)存</b><b class='flag-5'>泄漏</b>的方法和原則

    什么是內(nèi)存泄漏內(nèi)存泄漏有哪些現(xiàn)象

    內(nèi)存泄漏幾乎是很難避免的,不管是老手還是新手,都存在這個(gè)問(wèn)題,甚至 Windows 與 Linux 這類系統(tǒng)軟件也或多或少存在著內(nèi)存泄漏
    的頭像 發(fā)表于 09-05 17:24 ?9578次閱讀

    ThreadLocal基本內(nèi)容與用法

    下面我們就來(lái)看看道哥都用的ThreadLocal。 1 ThreadLocal你來(lái)自哪里 Since : 1.2 Author : Josh Bloch and Doug Lea 又是并發(fā)大佬們
    的頭像 發(fā)表于 10-13 11:39 ?425次閱讀

    什么是內(nèi)存泄漏?如何避免JavaScript內(nèi)存泄漏

    JavaScript 代碼中常見(jiàn)的內(nèi)存泄漏的常見(jiàn)來(lái)源: 研究內(nèi)存泄漏問(wèn)題就相當(dāng)于尋找符合垃圾回收機(jī)制的編程方式,有效避免對(duì)象引用的問(wèn)題。
    發(fā)表于 10-27 11:30 ?350次閱讀
    什么是<b class='flag-5'>內(nèi)存</b><b class='flag-5'>泄漏</b>?如何<b class='flag-5'>避免</b>JavaScript<b class='flag-5'>內(nèi)存</b><b class='flag-5'>泄漏</b>

    內(nèi)存泄漏如何避免

    的數(shù),那就是內(nèi)存溢出。 2. 內(nèi)存泄漏 內(nèi)存泄露 memory leak,是指程序在申請(qǐng)內(nèi)存后,無(wú)法釋放已申請(qǐng)的
    的頭像 發(fā)表于 11-10 11:04 ?692次閱讀
    <b class='flag-5'>內(nèi)存</b><b class='flag-5'>泄漏</b>如何<b class='flag-5'>避免</b>

    內(nèi)存泄漏會(huì)產(chǎn)生哪些后果

    內(nèi)存泄漏原因 內(nèi)存泄漏在C/C++這種不帶GC(Garbage Collection)的語(yǔ)言里,是一個(gè)經(jīng)常發(fā)生的問(wèn)題。因?yàn)闆](méi)有GC,所以分配的內(nèi)存
    的頭像 發(fā)表于 11-10 15:06 ?751次閱讀
    <b class='flag-5'>內(nèi)存</b><b class='flag-5'>泄漏</b>會(huì)產(chǎn)生哪些后果

    線程內(nèi)存泄漏問(wèn)題的定位

    記錄一個(gè)關(guān)于線程內(nèi)存泄漏問(wèn)題的定位過(guò)程,以及過(guò)程中的收獲。 1. 初步定位 是否存在內(nèi)存泄漏:想到內(nèi)存
    的頭像 發(fā)表于 11-13 11:38 ?571次閱讀
    線程<b class='flag-5'>內(nèi)存</b><b class='flag-5'>泄漏</b>問(wèn)題的定位

    內(nèi)存溢出與內(nèi)存泄漏:定義、區(qū)別與解決方案

    內(nèi)存溢出與內(nèi)存泄漏:定義、區(qū)別與解決方案? 內(nèi)存溢出和內(nèi)存泄漏是計(jì)算機(jī)科學(xué)中常見(jiàn)的問(wèn)題,在開發(fā)和
    的頭像 發(fā)表于 12-19 14:10 ?2345次閱讀

    C語(yǔ)言內(nèi)存泄漏問(wèn)題原理

    內(nèi)存泄漏問(wèn)題只有在使用堆內(nèi)存的時(shí)候才會(huì)出現(xiàn),棧內(nèi)存不存在內(nèi)存泄漏問(wèn)題,因?yàn)闂?/div>
    發(fā)表于 03-19 11:38 ?443次閱讀
    C語(yǔ)言<b class='flag-5'>內(nèi)存</b><b class='flag-5'>泄漏</b>問(wèn)題原理