成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

線上系統(tǒng)因?yàn)橐粋€(gè)ThreadLocal直接內(nèi)存飆升

開發(fā) 前端
在ThreadLocal的內(nèi)部有一個(gè)靜態(tài)內(nèi)部類ThreadLocalMap,這個(gè)才是真正存儲(chǔ)對(duì)象的Map,我們平時(shí)使用的set存儲(chǔ)的值實(shí)際上是存儲(chǔ)到這里面的.

[[437511]]

前言

大家對(duì)于ThreadLocal這一個(gè)都應(yīng)該聽說過的吧,不知道大家對(duì)于這個(gè)掌握的怎么樣了

這不,我那愛學(xué)習(xí)的表妹不知道又從哪里聽來了這個(gè)技術(shù)點(diǎn),回家就得意洋洋的給我說,表哥,我今天又學(xué)會(huì)了一個(gè)技術(shù)點(diǎn)ThreadLocal

哦,不錯(cuò)啊

你你這態(tài)度,好像不太信的樣子啊,表妹咬牙切齒的說著

沒沒沒,我信。我表妹那么聰明伶俐,肯定會(huì)

不行,你這態(tài)度太敷衍了,不信我給你講一遍

得,你也先別給我講了,你把你的Mac拿過來,我給你寫個(gè)東西

接過她的Mac,我三下五除二給她寫了一個(gè)小例子

  1. public class ThreadPool { 
  2.     private static final ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(10, 10, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<>()); 
  3.     public static void main(String[] args) throws InterruptedException { 
  4.         for (int i = 0; i < 1000; ++i) { 
  5.             poolExecutor.execute(new Runnable() { 
  6.                 @Override 
  7.                 public void run() { 
  8.                     ThreadLocal<BigObject> threadLocal = new ThreadLocal<>(); 
  9.                     threadLocal.set(new BigObject()); 
  10.                     // 其他業(yè)務(wù)代碼 
  11.                 } 
  12.             }); 
  13.             Thread.sleep(1000); 
  14.         } 
  15.     } 
  16.     static class BigObject { 
  17.         // 100M 
  18.         private byte[] bytes = new byte[100 * 1024 * 1024]; 
  19.     } 

你先看看這段代碼,給我說說你的理解

表妹眉頭一皺,你這是侮辱我的智商嗎,這不就是創(chuàng)建了一個(gè)線程池,然后使用for循環(huán)增加線程,往線程池里面提交一千個(gè)任務(wù)嗎

這也沒啥問題啊,每個(gè)任務(wù)會(huì)向ThreadLocal變量里面塞一個(gè)大對(duì)象,然后執(zhí)行其他業(yè)務(wù)邏輯

總之,看著沒啥大毛病,這就是表妹的結(jié)論

如果你覺得這段代碼沒啥問題,那看來你對(duì)ThreadLocal學(xué)的還是不夠徹底啊

代碼分析

來,我來給你透徹的說一遍,包教包會(huì)

先分析一下上面的代碼

  • 創(chuàng)建一個(gè)核心線程數(shù)和最大線程數(shù)都為10的線程池,保證線程池里一直會(huì)有10個(gè)線程在運(yùn)行。
  • 使用for循環(huán)向線程池中提交了100個(gè)任務(wù)。
  • 定義了一個(gè)ThreadLocal類型的變量,Value類型是大對(duì)象。
  • 每個(gè)任務(wù)會(huì)向threadLocal變量里塞一個(gè)大對(duì)象,然后執(zhí)行其他業(yè)務(wù)邏輯。
  • 由于沒有調(diào)用線程池的shutdown方法,線程池里的線程還是會(huì)在運(yùn)行。

說個(gè)結(jié)論

上面的代碼會(huì)造成內(nèi)存泄漏,會(huì)讓服務(wù)的內(nèi)存一直很高,即使GC之后也不會(huì)降低太多,這不是我們想要的結(jié)果

ThreadLocal存儲(chǔ)模型

在ThreadLocal的內(nèi)部有一個(gè)靜態(tài)內(nèi)部類ThreadLocalMap,這個(gè)才是真正存儲(chǔ)對(duì)象的Map,我們平時(shí)使用的set存儲(chǔ)的值實(shí)際上是存儲(chǔ)到這里面的

  1. static class ThreadLocalMap { 
  2.    // 定義一個(gè)table數(shù)組,存儲(chǔ)多個(gè)threadLocal對(duì)象及其value值 
  3.    private Entry[] table
  4.    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { 
  5.        table = new Entry[INITIAL_CAPACITY]; 
  6.        int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); 
  7.        table[i] = new Entry(firstKey, firstValue); 
  8.        size = 1; 
  9.        setThreshold(INITIAL_CAPACITY); 
  10.    } 
  11.    // 定義一個(gè)Entry類,key是一個(gè)弱引用的ThreadLocal對(duì)象 
  12.    // value是任意對(duì)象 
  13.   static class Entry extends WeakReference<ThreadLocal<?>> { 
  14.        /** The value associated with this ThreadLocal. */ 
  15.        Object value; 
  16.        Entry(ThreadLocal<?> k, Object v) { 
  17.            super(k); 
  18.            value = v; 
  19.        } 
  20.    } 
  21.    // 省略其他 

我們重點(diǎn)來看set正如上面代碼所示,ThreadLocalMap的內(nèi)部實(shí)際是一個(gè)Entry數(shù)組,而這個(gè)Entry對(duì)象就是Key和Value組成

重點(diǎn)來了,這個(gè)Key就是ThreadLocal實(shí)例本身,這個(gè)Value就是我們要存儲(chǔ)的真實(shí)的數(shù)據(jù)

大家看到這,是不是覺得很熟悉,沒錯(cuò),這個(gè)ThreadLocalMap就是一個(gè)Map而已,這個(gè)Map和普通的Map有兩點(diǎn)不同之處

1、Key、Value的存儲(chǔ)內(nèi)容的不同

2、ThreadLocalMap的Key是一個(gè)弱引用類型

其實(shí)吧,第一點(diǎn)也不算是不同,只是這里存儲(chǔ)的Key有點(diǎn)出乎我們的意料,這里重點(diǎn)的重點(diǎn)其實(shí)是這個(gè)第二點(diǎn),也就是這個(gè)弱引用類型,大家先記著,下面說

我們先來看一下ThreadLocal的get和set方法來驗(yàn)證一下我的說法

ThreadLocal的set方法

  1. public class ThreadLocal<T> { 
  2.  
  3.  public void set(T value) { 
  4.         Thread t = Thread.currentThread(); 
  5.         ThreadLocalMap map = getMap(t); 
  6.         if (map != null
  7.             map.set(this, value); 
  8.         else 
  9.             createMap(t, value); 
  10.     } 
  11.  
  12.     ThreadLocalMap getMap(Thread t) { 
  13.         return t.threadLocals; 
  14.     } 
  15.  
  16.     void createMap(Thread t, T firstValue) { 
  17.         t.threadLocals = new ThreadLocalMap(this, firstValue); 
  18.     } 
  19.     // 省略其他方法 

set的邏輯其實(shí)也是很簡(jiǎn)單的,獲取當(dāng)前線程的ThreadLocalMap,然后就直接往map里面添加Key和Value,而這個(gè)Key就是this,這個(gè)this就是ThreadLocal實(shí)例本身了

value就是我們要存儲(chǔ)的數(shù)據(jù)

這里需要注意一下,map的獲取是需要從Thread類對(duì)象里面取,看一下Thread類的定義。

  1. public class Thread implements Runnable { 
  2.     ThreadLocal.ThreadLocalMap threadLocals = null
  3.     //省略其他 

ThreadLocal的get方法

  1. class ThreadLocal<T> { 
  2.  
  3.     public T get() { 
  4.         Thread t = Thread.currentThread(); 
  5.         ThreadLocalMap map = getMap(t); 
  6.         if (map != null) { 
  7.             ThreadLocalMap.Entry e = map.getEntry(this); 
  8.             if (e != null
  9.                 return (T)e.value; 
  10.         } 
  11.         return setInitialValue(); 
  12.     } 

弱引用獲取當(dāng)前線程的ThreadLocalMap實(shí)例,如果不為空,直接用當(dāng)前ThreadLocal的實(shí)例來作為Key獲取Value即可

如果ThreadLocalMap為空,或者根據(jù)當(dāng)前的ThreadLocal實(shí)例獲取到的Value為空,則執(zhí)行setInitialValue()

而setInitialValue的內(nèi)部實(shí)現(xiàn)就是如果Map不為空,就設(shè)置鍵值對(duì),為空,則創(chuàng)建Map

ThreadLocal的內(nèi)部關(guān)系

這個(gè)圖畫的很清晰了

每個(gè)Thread線程會(huì)有一個(gè)threadlocals,這是一個(gè)ThreadLocalMap對(duì)象

通過這個(gè)對(duì)象,可以存儲(chǔ)線程的私有變量,就是通過ThreadLocal的set和get來操作

ThreadLocal本身不是一個(gè)容器,本身不存儲(chǔ)任何數(shù)據(jù),實(shí)際存儲(chǔ)數(shù)據(jù)的對(duì)象是ThreadLocalMap對(duì)象,操作的過程就類似于Map的put和get

這個(gè)ThreadLocalMap對(duì)象就是負(fù)責(zé)ThreadLocal真實(shí)存儲(chǔ)數(shù)據(jù)的對(duì)象,內(nèi)部的存儲(chǔ)結(jié)構(gòu)是Entry數(shù)組,這個(gè)Entry就是存儲(chǔ)Key和Value對(duì)象

Key就是ThreadLocal實(shí)例本身,而Value就是我們要存儲(chǔ)的真實(shí)數(shù)據(jù),而我們也從上面的源碼中看到了,存和取就是根據(jù)ThreadLocal實(shí)例來操作的

ThreadLocal內(nèi)存模型

圖中左邊是棧,右邊是堆。線程的一些局部變量和引用使用的內(nèi)存屬于Stack(棧)區(qū),而普通的對(duì)象是存儲(chǔ)在Heap(堆)區(qū)。

  • 線程運(yùn)行時(shí),我們定義的TheadLocal對(duì)象被初始化,存儲(chǔ)在Heap,同時(shí)線程運(yùn)行的棧區(qū)保存了指向該實(shí)例的引用,也就是圖中的ThreadLocalRef。
  • 當(dāng)ThreadLocal的set/get被調(diào)用時(shí),虛擬機(jī)會(huì)根據(jù)當(dāng)前線程的引用也就是CurrentThreadRef找到其對(duì)應(yīng)在堆區(qū)的實(shí)例,然后查看其對(duì)用的TheadLocalMap實(shí)例是否被創(chuàng)建,如果沒有,則創(chuàng)建并初始化。
  • Map實(shí)例化之后,也就拿到了該ThreadLocalMap的句柄,那么就可以將當(dāng)前ThreadLocal對(duì)象作為key,進(jìn)行存取操作。
  • 圖中的虛線,表示key對(duì)應(yīng)ThreadLocal實(shí)例的引用是個(gè)弱引用。

四種引用

強(qiáng)引用,一直活著:類似“Object obj=new Object()”這類的引用,只要強(qiáng)引用還存在,垃圾收集器永遠(yuǎn)不會(huì)回收掉被引用的對(duì)象實(shí)例。

弱引用,回收就會(huì)死亡被弱引用關(guān)聯(lián)的對(duì)象實(shí)例只能生存到下一次垃圾收集發(fā)生之前。當(dāng)垃圾收集器工作時(shí),無論當(dāng)前內(nèi)存是否足夠,都會(huì)回收掉只被弱引用關(guān)聯(lián)的對(duì)象實(shí)例。在JDK 1.2之后,提供了WeakReference類來實(shí)現(xiàn)弱引用。

軟引用,有一次活的機(jī)會(huì):軟引用關(guān)聯(lián)著的對(duì)象,在系統(tǒng)將要發(fā)生內(nèi)存溢出異常之前,將會(huì)把這些對(duì)象實(shí)例列進(jìn)回收范圍之中進(jìn)行第二次回收。如果這次回收還沒有足夠的內(nèi)存,才會(huì)拋出內(nèi)存溢出異常。在JDK 1.2之后,提供了SoftReference類來實(shí)現(xiàn)軟引用。

虛引用,也稱為幽靈引用或者幻影引用,它是最弱的一種引用關(guān)系。一個(gè)對(duì)象實(shí)例是否有虛引用的存在,完全不會(huì)對(duì)其生存時(shí)間構(gòu)成影響,也無法通過虛引用來取得一個(gè)對(duì)象實(shí)例。為一個(gè)對(duì)象設(shè)置虛引用關(guān)聯(lián)的唯一目的就是能在這個(gè)對(duì)象實(shí)例被收集器回收時(shí)收到一個(gè)系統(tǒng)通知。在JDK 1.2之后,提供了PhantomReference類來實(shí)現(xiàn)虛引用。

ThreadLocal中的弱引用和內(nèi)存泄漏

  1. static class ThreadLocalMap { 
  2.     // 定義一個(gè)Entry類,key是一個(gè)弱引用的ThreadLocal對(duì)象 
  3.     // value是任意對(duì)象 
  4.     static class Entry extends WeakReference<ThreadLocal<?>> { 
  5.         /** The value associated with this ThreadLocal. */ 
  6.         Object value; 
  7.         Entry(ThreadLocal<?> k, Object v) { 
  8.             super(k); 
  9.             value = v; 
  10.         } 
  11.     } 

我們先來看下Entry的實(shí)現(xiàn),Key會(huì)被保存到弱引用WeakReference中

這里的Key作為弱引用是關(guān)鍵,我們分兩種情況來討論

Key作為強(qiáng)引用的時(shí)候

引用ThreadLocal的對(duì)象被回收了,但是ThreadLocalMap還有ThreadLocal的強(qiáng)引用,所以如果沒有進(jìn)行手動(dòng)刪除的話,ThreadLocal是不會(huì)被GC回收的,也就是會(huì)導(dǎo)致Entry的內(nèi)存泄露

一句話,強(qiáng)引用的時(shí)候需要手動(dòng)刪除才會(huì)釋放內(nèi)存

Key作為弱引用的時(shí)候

引用ThreadLocal的對(duì)象被回收了之后,由于ThreadLocalMap持有的是ThreadLocal的弱引用,即使不會(huì)手動(dòng)刪除這個(gè)ThreadLocal,這個(gè)ThreadLocal也會(huì)被回收

前提是該對(duì)象只被弱引用所關(guān)聯(lián),別的強(qiáng)引用關(guān)聯(lián)不到!

而Value則是在下一次調(diào)用get、set、remove的時(shí)候進(jìn)行清除,才會(huì)被GC自動(dòng)回收

一句話,弱引用是多一層屏障,無外部強(qiáng)引用的時(shí)候,弱引用ThreadLocal會(huì)被GC回收,但是該ThreadLocal對(duì)應(yīng)的Value只有執(zhí)行set、get和remove的時(shí)候才會(huì)被清除

比較這兩種情況

由于ThreadLocalMap的生命周期是和Thread一樣的,因?yàn)樗荰hread內(nèi)部實(shí)現(xiàn)的,如果沒有手動(dòng)刪除對(duì)應(yīng)的key,都會(huì)導(dǎo)致內(nèi)存泄漏

而ThreadLocal使用弱引用,會(huì)多了一層保障,ThreadLocal在被清理之后,也就是Map中的key會(huì)變成null,在使用對(duì)應(yīng)value的時(shí)候就會(huì)將這個(gè)value進(jìn)行清除

但是!但是!但是!

使用弱引用并不代表不需要考慮內(nèi)存泄漏,只是多了一層屏障而已!

造成內(nèi)存泄漏的根源就是:ThreadLocalMap和Thread的生命周期一樣長(zhǎng),如果沒有手動(dòng)刪除對(duì)應(yīng)key,就會(huì)導(dǎo)致相應(yīng)的value不能及時(shí)得到清除,造成內(nèi)存泄漏

我們?cè)诰€上使用最多的就是線程池了,這樣問題就大了

你想啊,線程池里面有10個(gè)活躍線程,線程一直在運(yùn)行,不會(huì)停止,每次線程直接拿到過來用,然后用完之后會(huì)再次放到線程池中,此時(shí)線程并不會(huì)停止

也就是說這些線程的每一次使用都有可能產(chǎn)生新的ThreadLocal,而我們使用完對(duì)應(yīng)的ThreadLocal之后,如果不去手動(dòng)執(zhí)行remove刪除相應(yīng)的key,就會(huì)導(dǎo)致ThreadLocalMap中的Entry一直在增加,并且內(nèi)存是永遠(yuǎn)得不到釋放

這本身就是一個(gè)很恐怖的事情,再要是放到ThreadLocal中的對(duì)象還是超大對(duì)象,那后果不堪設(shè)想

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

綜合上面的分析,我們可以理解ThreadLocal內(nèi)存泄漏的前因后果,那么怎么避免內(nèi)存泄漏呢?答案就是:每次使用完ThreadLocal,建議調(diào)用它的remove()方法,清除數(shù)據(jù)。另外需要強(qiáng)調(diào)的是并不是所有使用ThreadLocal的地方,都要在最后remove(),因?yàn)樗麄兊纳芷诳赡苁切枰晚?xiàng)目的生存周期一樣長(zhǎng)的,所以要進(jìn)行恰當(dāng)?shù)倪x擇,以免出現(xiàn)業(yè)務(wù)邏輯錯(cuò)誤!

ThreadLocal的應(yīng)用場(chǎng)景

場(chǎng)景1:

ThreadLocal 用作保存每個(gè)線程獨(dú)享的對(duì)象,為每個(gè)線程都創(chuàng)建一個(gè)副本,這樣每個(gè)線程都可以修改自己所擁有的副本, 而不會(huì)影響其他線程的副本,確保了線程安全。

場(chǎng)景2:

ThreadLocal 用作每個(gè)線程內(nèi)需要獨(dú)立保存信息,以便供其他方法更方便地獲取該信息的場(chǎng)景。每個(gè)線程獲取到的信息可能都是不一樣的,前面執(zhí)行的方法保存了信息后,后續(xù)方法可以通過ThreadLocal 直接獲取到,避免了傳參,類似于全局變量的概念。

舉個(gè)具體的使用例子

比如可以用于保存線程不安全的工具類,典型的需要使用的類就是 SimpleDateFormat。

在這種情況下,每個(gè)Thread內(nèi)都有自己的實(shí)例副本,且該副本只能由當(dāng)前Thread訪問到并使用,相當(dāng)于每個(gè)線程內(nèi)部的本地變量,這也是ThreadLocal命名的含義。因?yàn)槊總€(gè)線程獨(dú)享副本,而不是公用的,所以不存在多線程間共享的問題。

這種線程不安全的工具類如果需要在很多的線程中同時(shí)使用的話,任務(wù)數(shù)量巨大的情況下,也就是需要線程數(shù)巨多的情況下,這個(gè)不安全我們就需要讓它變得安全

比如使用Synchronized鎖,這樣可以解決,但是Synchronized會(huì)讓線程進(jìn)入一個(gè)排隊(duì)的狀態(tài),大大降低整體的工作效率

我們?cè)诰€上一般使用線程池,ThreadLocal再合適不過了,ThreadLocal給每個(gè)線程維護(hù)一個(gè)自己的simpleDateFormat對(duì)象,這個(gè)對(duì)象在線程之間是獨(dú)立的,互相沒有關(guān)系的。這也就避免了線程安全問題。與此同時(shí),simpleDateFormat對(duì)象還不會(huì)創(chuàng)造過多,線程池有多少個(gè)線程,所以需要多少個(gè)對(duì)象即可。

再說一個(gè)形象的場(chǎng)景

每個(gè)線程內(nèi)需要保存類似于全局變量的信息(例如在攔截器中獲取的用戶信息),可以讓不同方法直接使用,避免參數(shù)傳遞的麻煩卻不想被多線程共享(因?yàn)椴煌€程獲取到的用戶信息不一樣)。

例如,用 ThreadLocal 保存一些業(yè)務(wù)內(nèi)容(用戶權(quán)限信息、從用戶系統(tǒng)獲取到的用戶名、用戶ID 等),這些信息在同一個(gè)線程內(nèi)相同,但是不同的線程使用的業(yè)務(wù)內(nèi)容是不相同的。

在線程生命周期內(nèi),都通過這個(gè)靜態(tài) ThreadLocal 實(shí)例的 get() 方法取得自己 set 過的那個(gè)對(duì)象,避免了將這個(gè)對(duì)象(如 user 對(duì)象)作為參數(shù)傳遞的麻煩。

這其實(shí)就是類似于一種責(zé)任鏈的模式

 

責(zé)任編輯:姜華 來源: Java賊船
相關(guān)推薦

2024-02-04 16:14:38

線程開發(fā)

2022-03-23 18:00:34

循環(huán)CPU線程

2021-05-07 18:12:32

ThreadLocal面試項(xiàng)目

2022-04-08 08:48:16

線上事故日志訂閱者

2025-06-30 09:37:26

Linux服務(wù)器

2011-09-14 10:08:07

Beanstalkd

2020-06-22 07:47:46

提交面試官訂單

2022-08-26 07:33:49

內(nèi)存JVMEntry

2018-01-18 22:49:16

2024-12-09 15:00:00

C++20代碼標(biāo)記

2023-03-06 08:41:32

CPU使用率排查

2022-05-31 08:35:05

RocketMQACK客戶端

2018-08-17 08:44:37

服務(wù)器內(nèi)存排查

2024-08-14 08:35:38

sql數(shù)據(jù)庫OOM 異常

2018-09-18 09:38:11

RPC遠(yuǎn)程調(diào)用網(wǎng)絡(luò)通信

2021-08-10 09:58:59

ThreadLocal內(nèi)存泄漏

2023-05-29 07:17:48

內(nèi)存溢出場(chǎng)景

2018-10-25 15:24:10

ThreadLocal內(nèi)存泄漏Java

2024-10-31 09:24:42

2022-05-09 14:09:23

多線程線程安全
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 久久日韩精品 | 91久久夜色 | 亚洲精品4| 五月免费视频 | 精品一区二区三区91 | 色爱综合网 | 久久久久国产 | 99精品欧美一区二区三区 | 性色的免费视频 | 国产精品福利视频 | 中文字幕成人免费视频 | 99pao成人国产永久免费视频 | 日韩一区二区在线看 | 免费视频成人国产精品网站 | 日韩综合在线 | 久久激情视频 | 国产蜜臀97一区二区三区 | 在线观看视频亚洲 | 国产一区91在线 | 高清成人av | 五月婷婷在线播放 | 国产色视频网站 | 成人精品高清 | 日韩国产中文字幕 | 天天操操操操操 | 免费成人高清在线视频 | 久久精品久久久久久 | 日韩一区不卡 | 亚洲国产一区二区三区在线观看 | 欧美日日日日bbbbb视频 | 国产精品久久久久久网站 | 精品日韩一区二区三区av动图 | 水蜜桃久久夜色精品一区 | 成人二区| av一级在线观看 | www.欧美视频 | 精品伊人| h视频在线免费 | 自拍偷拍第一页 | 午夜小视频在线播放 | 91精品无人区卡一卡二卡三 |