最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

來(lái)聊聊ThreadLocal內(nèi)存泄露分析

2022-04-21 13:30 作者:指南針畢業(yè)設(shè)計(jì)  | 我要投稿

前幾天有個(gè)學(xué)生問(wèn)我ThreadLocal存在不存在內(nèi)存泄漏,趁此機(jī)會(huì)和大家聊聊ThreadLocal到底存在不存在內(nèi)存泄漏以及怎么避免。



Thread中的threadLocals屬性

一切都要從?Thread?的一個(gè)屬性?threadLocals?說(shuō)起,讓我們看下這個(gè)屬性的介紹:

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

這個(gè)?threadLocals?屬性是一個(gè)?ThreadLocal?里的靜態(tài)類(lèi)?ThreadLocalMap?,它是一個(gè)?map,并且是由?ThreadLocal?進(jìn)行維護(hù)管理的。

那么這個(gè)?threadLocals?,也就是這個(gè)?map?里,存的是什么呢?

我們來(lái)看?ThreadLocalMap?:

static class ThreadLocalMap { ? ?static class Entry extends WeakReference<ThreadLocal<?>> { ? ? ? ?Object value; ? ? ? ?Entry(ThreadLocal<?> k, Object v) { ? ? ? ? ? ?super(k); ? ? ? ? ? ?value = v; ? ? ? ?} ? ?} }

介紹里說(shuō),ThreadLocalMap?是一個(gè)定制化的?hash map,在?Entry?里,以鍵值對(duì)的形式存儲(chǔ)著?ThreadLocal?對(duì)象和?value。

于是?Thread?中?threadLocals?屬性和?ThreadLocal?的關(guān)系簡(jiǎn)圖就如下所示:

threadLocals.png

注意,這里?Entry?里的?key?,即?ThreadLocal?對(duì)象是以弱引用的形式存在的,這將是本文內(nèi)存泄露分析的重點(diǎn)之一,但這里先不談,再繼續(xù)講講?Thread?和?ThreadLocal?的關(guān)系。

Thread和ThreadLocal的關(guān)系

先上一段示例代碼:

public class ThreadLocalDemo { ? ?private static ThreadLocal<Weapon> weaponThreadLocal = new ThreadLocal<Weapon>() { ? ? ? ?@Override ? ? ? ?protected Weapon initialValue() { ? ? ? ? ? ?return new Weapon(); ? ? ? ?} ? ?}; ? ?private static class Player extends Thread { ? ? ? ?@Override ? ? ? ?public void run() { ? ? ? ? ? ?weaponThreadLocal.get().level += ThreadLocalRandom.current().nextInt(5); ? ? ? ? ? ?System.out.println(getName() + " level: " + weaponThreadLocal.get().level); ? ? ? ? ? ?weaponThreadLocal.get().combatEff = weaponThreadLocal.get().level * 10; ? ? ? ? ? ?System.out.println(getName() + " combatEff: " + weaponThreadLocal.get().combatEff); ? ? ? ?} ? ?} ? ?public static void main(String[] args) { ? ? ? ?Player player1 = new Player(); ? ? ? ?Player player2 = new Player(); ? ? ? ?player1.start(); ? ? ? ?player2.start(); ? ?} ? ?private static class Weapon { ? ? ? ?int level; ? ? ? ?int combatEff; ? ? ? ?public Weapon() { ? ? ? ? ? ?level = 1; ? ? ? ? ? ?combatEff = 10; ? ? ? ?} ? ?} }

在上述代碼中,有兩個(gè)?Player?,他們進(jìn)行一場(chǎng)游戲,每個(gè)人在游戲開(kāi)始時(shí)都會(huì)有一把武器?Weapon?。這把武器在游戲開(kāi)始時(shí)對(duì)每個(gè)人來(lái)說(shuō)是公平的,它的等級(jí)(level)和戰(zhàn)斗力 (Combat Effectiveness)都是一個(gè)固定值(由?ThreadLocal?初始化)。隨著游戲的進(jìn)行,他們的武器等級(jí)會(huì)升級(jí),戰(zhàn)斗力會(huì)變強(qiáng),但升多少級(jí)、變強(qiáng)多少就看造化了(由?ThreadLocalRandom?產(chǎn)生隨機(jī)數(shù))

看看運(yùn)行之后的結(jié)果吧:

Thread-1 level: 3 Thread-0 level: 5 Thread-1 combatEff: 30 Thread-0 combatEff: 50

看來(lái)線(xiàn)程0運(yùn)氣更好一點(diǎn)。

好了,上述的例子只是為了接下來(lái)的說(shuō)明做一個(gè)鋪墊,下面就從上述例子開(kāi)始談?wù)?Thread?和?ThreadLocal?的關(guān)系。

一般來(lái)說(shuō),可以認(rèn)為?ThreadLocal?解決了線(xiàn)程間共享變量的問(wèn)題,即?ThreadLocal?為每個(gè)線(xiàn)程維護(hù)了一個(gè)共享變量的副本,多個(gè)線(xiàn)程在修改這個(gè)變量時(shí)(其實(shí)是修改自己的變量副本),不存在線(xiàn)程安全問(wèn)題,效率也很高。所以,在上述例子中,對(duì)于一個(gè)共享變量,ThreadLocal?提供了兩個(gè)功能:



  • 統(tǒng)一設(shè)置初始值




  • 每個(gè)線(xiàn)程對(duì)該值的修改互不影響,做到變量隔離


那么乍一看,Thread?和?ThreadLocal?的關(guān)系好像是這樣:

threadLocal-key-value.png

可是這樣是不對(duì)的,如果理解成這樣,我上面?Thread中的threadLocals屬性?那一大段就白說(shuō)了。

再把那段的內(nèi)容概括下,Thread?里有?ThreadLocalMap,而ThreadLocal?和?value?以鍵值對(duì)的形式存儲(chǔ)在?ThreadLocalMap?中,所以?Thread?和?ThreadLocal?的關(guān)系應(yīng)該是這樣:

thread-threadLocal.png

當(dāng)我們調(diào)用?ThreadLocal?的?get()、set()?和?remove()?操作?Thread??對(duì)應(yīng)?的?value?時(shí),實(shí)際上是由?Thread?的?ThreadLocalMap?在操作?ThreadLocal?對(duì)應(yīng)的?value。

對(duì)應(yīng)上述的代碼示例,如果我們?cè)俳o每個(gè)?Player?新增一個(gè)?Life?的共享變量,又多出一個(gè)管理?Life?變量的?ThreadLocal,那么它們的示意圖就該是這樣的:

add-life.png

至此,Thread?和?ThreadLocal?的關(guān)系應(yīng)該說(shuō)明清楚了,下面就開(kāi)始分析?ThreadLocal?中存在的內(nèi)存泄露問(wèn)題。

ThreadLocal中內(nèi)存泄露問(wèn)題分析

要分析?ThreadLocal?中的內(nèi)存泄露問(wèn)題,得看一張?Thread?和?ThreadLocal?從內(nèi)存角度分析的關(guān)系圖:

memory.png

從上圖進(jìn)行后續(xù)分析。

分析一

從上圖可知,Thread?對(duì)象里的?threadLocals?持有?ThreadLocalMap?對(duì)象,Entry?對(duì)象。那么當(dāng)線(xiàn)程執(zhí)行完畢,線(xiàn)程對(duì)象被回收,ThreadLocalMap?也會(huì)被回收。由于?Entry?持有?Weapon?對(duì)象,即?value?對(duì)象的引用,value?對(duì)象也會(huì)被回收。除了?ThreadLocal?對(duì)象,隨著線(xiàn)程執(zhí)行完畢,所有對(duì)象都會(huì)被回收,皆大歡喜,沒(méi)有內(nèi)存泄露。

分析二

若線(xiàn)程還在執(zhí)行中,而?ThreadLocal?對(duì)象引用被置為?null,即現(xiàn)在不需要?ThreadLocal?了,那么其實(shí)?Weapon?也失去了意義,照理說(shuō)是該把?Weapon?對(duì)象回收的,那么怎么回收呢?

一旦?ThreadLocal?對(duì)象引用被置為?null,那么由于?Entry?對(duì)象持有的是?ThreadLocal?對(duì)象的弱引用,那么?ThreadLocal?對(duì)象就會(huì)在下一次?YGC?時(shí)被回收。此時(shí),Entry?對(duì)象的?key?為空了,value?無(wú)法訪(fǎng)問(wèn)到了,怎么回收呢?原來(lái)對(duì)此情況早有設(shè)計(jì),當(dāng)每次在?get()、set()、remove()?ThreadLocalMap中的值的時(shí)候,都會(huì)自動(dòng)將?key?為空的?value?置為空,那么?value?對(duì)象也能夠被回收了,不存在內(nèi)存泄露了。

那么內(nèi)存泄露到底存在于哪里呢?

分析三

在我們使用?ThreadLocal?時(shí),通常是將它作為私有靜態(tài)變量使用的。如果把?ThreadLocal?作為成員對(duì)象使用,那么每個(gè)使用的?ThreadLocal?的類(lèi)都可能創(chuàng)建一個(gè)?ThreadLocal?對(duì)象,而?ThreadLocal?其實(shí)是使用?ThreadLocalMap?對(duì)線(xiàn)程和?value?進(jìn)行管理的,多個(gè)?ThreadLocal?對(duì)象沒(méi)有意義,會(huì)造成內(nèi)存浪費(fèi)。

但另一方面,把?ThreadLocal?作為靜態(tài)變量使用的話(huà),它就無(wú)法被置空了。ThreadLocal?無(wú)法被置空,就無(wú)法通過(guò)觸發(fā)弱引用機(jī)制來(lái)回收?ThreadLocal?對(duì)象,Entry?里的?key?就不會(huì)為空,就無(wú)法通過(guò)分析二的方法回收?value?對(duì)象。

這就是內(nèi)存泄露的由來(lái)了。

總結(jié)一下內(nèi)存泄露的兩個(gè)條件:



  • ThreadLocal?作為靜態(tài)變量使用




  • 線(xiàn)程未執(zhí)行完畢


在此情況下,線(xiàn)程中的?ThreadLocalMap?中的鍵值對(duì)會(huì)越堆越多,可能產(chǎn)生內(nèi)存溢出問(wèn)題。

解決辦法

如果線(xiàn)程還在執(zhí)行,那么在?ThreadLocal?的使命完成后,調(diào)用它的?remove()?方法,該方法會(huì)把?Entry?里的?key?置空,就可以回收?value?對(duì)象了。(這里?remove()?方法還有待研究),但用就對(duì)了。

線(xiàn)程池臟數(shù)據(jù)分析

再分析一下?ThreadLocal?和線(xiàn)程池一起使用時(shí)的臟數(shù)據(jù)問(wèn)題。(其實(shí)?ThreadLocal?的內(nèi)存泄露也多數(shù)出現(xiàn)在和線(xiàn)程池一起使用的情況)

由以上分析可知,線(xiàn)程執(zhí)行完畢,線(xiàn)程對(duì)象被回收,一切問(wèn)題都不會(huì)存在。

若線(xiàn)程在線(xiàn)程池中復(fù)用,且不調(diào)用?remove?方法,那么線(xiàn)程在執(zhí)行完畢一次任務(wù)并復(fù)用時(shí),從?ThreadLocalMap?中取出來(lái)的?value?就是上一次執(zhí)行任務(wù)完畢后的值。這時(shí)候,倘若我們的線(xiàn)程在執(zhí)行每次任務(wù)時(shí),沒(méi)有調(diào)用?set()?方法對(duì)?value?重新賦值,那么業(yè)務(wù)邏輯肯定就錯(cuò)了。

解決辦法



  • 線(xiàn)程池復(fù)用時(shí),在線(xiàn)程的?run()?方法中要調(diào)用?ThreadLocal?的?set()?方法對(duì)?value?重新賦值




  • 在線(xiàn)程的?run()?方法最后調(diào)用?ThreadLocal?的?remove()?方法



來(lái)聊聊ThreadLocal內(nèi)存泄露分析的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
修文县| 东宁县| 安远县| 开原市| 嘉黎县| 秦安县| 麻栗坡县| 江津市| 藁城市| 清镇市| 赣榆县| 吉林省| 安顺市| 湖州市| 吉水县| 图木舒克市| 柘城县| 凌海市| 克山县| 永福县| 沐川县| 德兴市| 上高县| 建阳市| 元朗区| 江永县| 盐城市| 集安市| 绥宁县| 鹤岗市| 万山特区| 宁乡县| 峨眉山市| 垦利县| 根河市| 明星| 鹿泉市| 大连市| 西峡县| 安阳县| 皮山县|