日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > windows >内容正文

windows

Netty源码学习8——从ThreadLocal到FastThreadLocal(如何让FastThreadLocal内存泄漏doge)

發布時間:2023/12/24 windows 30 coder
生活随笔 收集整理的這篇文章主要介紹了 Netty源码学习8——从ThreadLocal到FastThreadLocal(如何让FastThreadLocal内存泄漏doge) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

系列文章目錄和關于我

一丶引入

在前面的netty源碼學習中經??吹紽astThreadLocal的身影,這一篇我們將從ThreadLocal說起,來學習FastThreadLocal的設計(《ThreadLocal源碼學習筆記》)

二丶從ThreadLocal說起

ThreadLocal是JDK中實現線程隔離的一個工具類。實現線程隔離maybe你第一反應會做出Map<Thread,V>的設計,但是Map在高并發的情況下需要使用鎖or cas 來實現線程安全(如ConcurrentHashMap)鎖or cas都將帶來額外的開銷。

那么ThreadLocal是如何實現的昵:

1.ThreadLocal基本結構

其基本結構如下:

  • 每一個Thread對象都有一個名為threadLocals類型為ThreadLocal.ThreadLocalMap的屬性。
  • ThreadLocal.ThreadLocalMap對象內部存在一個Entry數組,其中存儲的Entry對象key是ThreadLocal,value便是我們綁定在線程上的值。
  • ThreadLocal可以做到線程隔離是由于每一個線程對象持有一個ThreadLocalMap,每一個線程對ThreadLocalMap的處理是互不影響的。

2.ThreadLocal的優秀設計

2.1 線程內部屬性實現線程隔離,避免鎖競爭

如果使用Map<Thread,V>,不可避免的要處理線程安全問題,但是ThreadLocal巧妙的在Thread內部使用ThreadLocalMap來避免此問題

2.2 對開發者屏蔽細節

如果你不深入看ThreadLocal的源碼,maybe你會認為是ThreadLocal里面存儲了數據。你只需要使用ThreadLocal#get,set,remove即可,你完全不需要關注其底層細節。

對開發者來說好像ThreadLocal就是存儲貨物的倉庫,其實ThreadLocal只是打開倉庫的鑰匙(使用ThreadLocal去ThreadLocalMap獲取value)

2.3 巧妙的利用弱引用避免內存泄漏

上面我們了解到ThreadLocal是ThreadLocalMap中的key,思考一下,如果使用ThreadLocal#set但是沒調用ThreadLocal#remove,是不是意味著ThreadLocalMap中一直會存儲這個ThreadLocal和對應的Value昵?

答案是No,ThreadLocal巧妙的使用了弱引用來解決這個問題

ThreadLocalMap中存儲的Entry繼承了WeakRefrence,根據上面的源碼可以看出Entry對ThreadLocal是弱引用

  • 因此:在垃圾回收器線程掃描它所管轄的內存區域的過程中,一旦發現了只具有弱引用指向的對象,不管當前內存空間足夠與否,都會回收它的內存。
  • 也就是說,如果ThreadLocal失去強引用(比如方法中局部變量,方法結束了也就失去了強引用),只存在Entry的弱引用,在發生GC的時候將回收ThreadLocal==>從而帶導致Entry的key為null

結合下圖看一下

細心的朋友這時候會指出:“key被回收了,value還存在哦,一樣可能存在內存泄漏哦”

是的,但是ThreadLocal還留了一手:即在下次調用其他ThreadLocal#get,set的時候,會幫助我們清理

清理什么?清理entry數組中key為null的entry對象

為什么可以清理,因為此Entry中的ThreadLocal失去了強引用,不會再被使用到了

妙!

2.4 使用線性探測法,而不是拉鏈法

上面我們說到每一個Thread中有一個ThreadLocalMap,其內部使用Entry數組保存多個ThreadLocal和ThreadLocal#set傳入的value

ThreadLocal#get就是從Entry數組中拿出Entry從而獲取value

那么怎么根據ThreadLocal從table中快速定位到Entry昵?hash又是hash,使用hash和數組長度取模即可!

Hash雖好,但是不要忘記Hash沖突哦!ThreadLocal解決hash沖突使用了線性探測法,而不是拉鏈法。

下圖是拉鏈法

下圖是線性探測法:如果找不到可以存放的位置,那么繼續探測下去,直至擴容

那為什么說ThreadLocal使用線性探測法妙昵?

  • 空間效率:ThreadLocal使用數組存儲數據,意味著數據在內存上是連續的,可以更好的利用CPU緩存減少尋址開銷。如果使用拉鏈法將Entry來需要額外的保存下一個元素的引用指針,帶來額外的開銷
  • 時間效率:通常ThreadLocal不會存儲太多元素,線性探測法在處理沖突時更快——因為數組存儲在內存上更加連續,可以更好的利用內存預讀能力,避免了鏈表內存引用導致了緩存未命中。

其中時間效率這一點是建立在ThreadLocalMap中不會存儲太多元素導致hash沖突嚴重的情況下,如果元素太多ThreadLocalMap也會進行擴容

如上:當前元素大于負載的3/4那么進行擴容

三丶FastThreadLocal 源碼淺析

上面說了ThreadLocal的原理和其優秀設計,那么為什么還需要FastThreadLocal昵?

如同FastThreadLocal的名字一樣,它在高并發的情況下擁有更高的性能!

1.FastThreadLocal最佳實踐

我們結合Netty源碼看看netty是如何使用FastThreadLocal的

  • 使用FastThreadLocalThread

    netty在創建EventLoopGroup中的線程的時候,默認使用DefaultThreadFactory,它會創建出FastThreadLocalThread

    至于為什么要是有FastThreadLocalThread,我們后面再分析

  • 將Runnable包裝為FastThreadLocalRunnable

    Netty會使用FastThreadLocalRunnable對原Runnable進行包裝,確保Runnable指向完后進行FastThreadLocal#removeAll釋放

    這一點再工作也經常使用,比如在分布式鏈路追蹤使用多線程處理業務邏輯,也需要將traceId對應的ThreadLocal進行傳遞和釋放,也是類似的手法。

  • 使用

    使用上和ThreadLocal類似

2.FastThreadLocalThread

可以看到FastThreadLocalThread是繼承了Thread,其中內部有一個InternalThreadLocalMap類型的屬性,這便是FastThreadLocal實現的奧秘。

3.InternalThreadLocalMap

InternalThreadLocalMap 中有兩個關鍵的屬性

  • ThreadLocal<InternalThreadLocalMap> slowThreadLocalMap,如果使用了FastThreadLocal,但是當前線程不是FastThread,那么會從這個ThreadLocal中獲取InternalThreadLocalMap

  • indexedVariables,除0之外的位置存儲線程隔離數據,0位置存儲所有的FastThreadLocal對象

3.FastThreadLocal源碼解析

3.1 get

可以看到get就是獲取當前線程的InternalThreadLocalMap,然后根據index獲取內容(如果是缺省值,那么會調用initialize方法進行初始化)

每一個FastThreadLocal對應一個唯一的index,在FastThreadLocal構造的時候調用InternalThreadLocalMap#nextVariableIndex產生(使用AtomicInteger自旋+cas產生)

如下是InternalThreadLocalMap#get方法源碼,可以看到根據當前線程是否是FastThreadLocalThread有不同的動作

如果是FastThreadLocalThread那么直接獲取屬性即可

如果非FastThreadLocalThread那么從ThreadLocal<InternalThreadLocalMap> slowThreadLocalMap中獲取

3.2 initialize

如果FastThreadLocal中沒用值,那么會調用initialValue進行初始化,initialValue是netty留給子類的擴展的方法

初始化之后會設置到InternalThreadLocalMap中,并調用addToVariablesToRemove將當前FastThreadLocal加入到variablesToRemove中,variablesToRemove位于InternalThreadLocalMap數組的0位置,即如下紅色框內容

3.3 set

可以看到如果存入的值不是缺省值,那么調用setKnownNotUnset進行設置

反之調用remove進行刪除

3.3.1 setKnownNotUnset

setIndexedVariable 就是向InternalThreadLocalMap中設置內容,

  • 在當前index小于數組長度的時候會直接進行設置

    如果舊值是UNSET缺省值那么說明之前沒用設置過,進而調用addToVariablesToRemove將當前FastThreadLocal設置到InternalThrealLocal數組下標為1的Set中

  • 如果當前index大于等于數組長度,相當于出現了hash沖突,這時候不會進行拉鏈,也不會進行線性探測,而是擴容,擴容邏輯如下

    首先是擴容到最接近當前index且大于index的2次冪大?。ê蚳ashMap一個道理)然后進行Arrays#copy實現數組拷貝,并存儲當前值

    這里可以看出FastThreadLocal快在哪里,設置值的時候使用擴容來解決hash沖突,雖然導致了一些空間的浪費,但是這也使得get的時候可以根據index直接獲取數據,避免了線性探測的尋址,從而有更高的性能!

3.4 remove

remove分為兩步,一是從InternalThreadLocalMap中移除index對應的元素,然后從InternalThreadLocal下標為0的Set中刪除

3.5 removeAll

FastThreadLocalRunnable在run方法指向完后自動指向此方法,即刪除當前線程所有的FastThreadLocal內容,避免內存泄漏

四丶總結與思考

1.FastThreadLocal快在哪里

空間換時間,ThreadLocal慢在線性探測,那么直接通過更大數組空間的開辟,避免線性探測,這是一種空間換時間的思想

2. FastThreadLocal為什么不使用弱引用

追求極致的性能,使用弱引用帶來如下缺點

  • GC開銷:弱引用需要GC垃圾收集器額外的工作來確定何時回收對象,netty這種對性能敏感的網絡框架,頻繁的gc帶來不可預測的延遲

  • 訪問速度:使用弱引用可以讓Entry中key被回收,但是value還是存在,因此ThreadLocal會在get,set,等方法中檢測key為null的元素進行刪除,這也會帶來一定的開銷

  • 顯示控制:上面我們看到,FastThreadLocalThread會將runnable進行包裝保證最后進行釋放,一定程度上保證

3.如何讓FastThreadLocal內存泄漏 doge

結合FastThreadLocal的原理,我們只要我不顯示釋放,也不讓Runnable保證為FastThreadLocalRunnable,那么就不會被釋放

如上這個例子,會持續輸出 "泄露啦",但是如果使用ThreadLocal,再下次使用ThreadLocal的get,set方法的時候就會自動進行清理!

總結

以上是生活随笔為你收集整理的Netty源码学习8——从ThreadLocal到FastThreadLocal(如何让FastThreadLocal内存泄漏doge)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。