📫作者簡介:小明Java問道之路,2022年度博客之星全國TOP3,專注于后端、中間件、計算機底層、架構設計演進與穩定性建設優化,文章內容兼具廣度、深度、大廠技術方案,對待技術喜歡推理加驗證,就職于知名金融公司后端高級工程師。
? ? ? ? ? ?
🏆 2022博客之星TOP3 | CSDN博客專家 | 后端領域優質創作者 | CSDN內容合伙人
🏆 InfoQ(極客邦)簽約作者、阿里云專家 | 簽約博主、51CTO專家 | TOP紅人、華為云享專家
? ? ? ? ?
?🔥如果此文還不錯的話,還請👍關注、點贊、收藏三連支持👍一下博主~?
🍅 文末獲取聯系 🍅??👇🏻 精彩專欄推薦訂閱收藏 👇🏻
專欄系列(點擊解鎖)
學習路線(點擊解鎖)
知識定位
🔥Redis從入門到精通與實戰🔥
Redis從入門到精通與實戰
圍繞原理源碼講解Redis面試知識點與實戰
🔥MySQL從入門到精通🔥
MySQL從入門到精通
全面講解MySQL知識與企業級MySQL實戰 🔥計算機底層原理🔥
深入理解計算機系統CSAPP
以深入理解計算機系統為基石,構件計算機體系和計算機思維
Linux內核源碼解析
圍繞Linux內核講解計算機底層原理與并發
🔥數據結構與企業題庫精講🔥
數據結構與企業題庫精講
結合工作經驗深入淺出,適合各層次,筆試面試算法題精講
🔥互聯網架構分析與實戰🔥
企業系統架構分析實踐與落地
行業最前沿視角,專注于技術架構升級路線、架構實踐
互聯網企業防資損實踐
互聯網金融公司的防資損方法論、代碼與實踐
🔥Java全棧白寶書🔥
精通Java8與函數式編程
本專欄以實戰為基礎,逐步深入Java8以及未來的編程模式
深入理解JVM
詳細介紹內存區域、字節碼、方法底層,類加載和GC等知識
深入理解高并發編程
深入Liunx內核、匯編、C++全方位理解并發編程
Spring源碼分析
Spring核心七IOC/AOP等源碼分析
MyBatis源碼分析
MyBatis核心源碼分析
Java核心技術
只講Java核心技術
本文目錄
本文導讀
一、ThreadLocal是什么
二、ThreadLocal的數據結構
三、ThreadLocal源碼解析?
1、?ThreadLocal的set()方法
2、?ThreadLocal的get()方法
3、?ThreadLocal的remove()方法
四、ThreadLocal使用場景
五、ThreadLocal內存泄露原因
六、如何正確的使用ThreadLocal
七、ThreadLocal為什么不將key設置為強引用
總結
本文導讀
本文講解ThreadLocal是什么、ThreadLocal的數據結構以及ThreadLocal源碼set()/get()/remove()解析,ThreadLocal使用場景,如何正確的使用ThreadLocal,ThreadLocal內存泄露原因。
一、ThreadLocal是什么
ThreadLocal中填充的變量屬于當前線程,該變量對其他線程而言是隔離的,也就是說該變量是當前線程獨有的變量。
ThreadLocal為每一個線程都提供了變量的副本,使得每個線程在某一時間訪問到的并不是同一個對象,這樣就隔離了多個線程對數據的數據共享。
ThreadLocal 提供了線程本地的實例。ThreadLocal 變量通常被private static修飾。當一個線程結束時,它所使用的所有 ThreadLocal 相對的實例副本都可被回收。
ThreadLocal 適用于每個線程需要自己獨立的實例且該實例需要在多個方法中被使用,也即變量在線程間隔離而在方法或類間共享的場景。
二、ThreadLocal的數據結構
ThreadLocal是作為當前線程中屬性ThreadLocalMap集合中的某一個Entry的key值Entry(threadlocl,value),雖然不同的線程之間threadlocal這個key值是一樣,但是不同的線程所擁有的ThreadLocalMap是獨一無二的,也就是不同的線程間同一個ThreadLocal(key)對應存儲的值(value)不一樣,從而到達了線程間變量隔離的目的,但是在同一個線程中這個value變量地址是一樣的。?
1、每個Thread線程內部都有一個Map(ThreadLocalMap)
2、Map里面存儲ThreadLocal對象(key)和線程的變量副本(value)
3、Thread內部的Map是由ThreadLocal維護的,由ThreadLocal負責向map獲取和設置線程的變量值。
4、對于不同的線程,每次獲取副本值時,別的線程并不能獲取到當前線程的副本值,形成了副本的隔離互不干擾。
三、ThreadLocal源碼解析?
1、?ThreadLocal的set()方法
ThreadLocal? set賦值的時候首先會獲取當前線程thread,獲取thread線程中的ThreadLocalMap屬性。如果map屬性不為空,則直接更新value值,如果map為空,則實例化threadLocalMap,并將value值初始化。
public void set(T value) {Thread t = Thread.currentThread(); // 1、獲取當前線程// 2、獲取線程中的threadLocalMap ,如果threadLocalMap不為空直接更新要保存的變量值// 否則創建threadLocalMap 并賦值ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value); // 初始化thradLocalMap 并賦值
}
ThreadLocalMap 是 ThreadLocal 的內部靜態類,而它的構成主要是用Entry來保存數據 ,而且還是繼承的弱引用。在Entry內部使用ThreadLocal作為key,使用我們設置的value作為value。
static class ThreadLocalMap {static class Entry extends WeakReference<ThreadLocal<?>> { // Java弱引用Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}}
}
2、?ThreadLocal的get()方法
public T get() {Thread t = Thread.currentThread(); // 1、獲取當前線程ThreadLocalMap map = getMap(t); // 2、獲取當前線程的ThreadLocalMapif (map != null) { // 3、如果map數據不為空,獲取threalLocalMap中存儲的值ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}// 如果是數據為null則初始化,TheralLocalMap中存放key為threadLocal值為nullreturn setInitialValue();
}
3、?ThreadLocal的remove()方法
remove方法直接將ThrealLocal對應的值從當前相差Thread中的ThreadLocalMap中刪除,?
public void remove() {ThreadLocalMap m = getMap(Thread.currentThread());if (m != null)m.remove(this);
}
四、ThreadLocal使用場景
ThreadLocal 適用于如下兩種場景:1、每個線程需要有自己單獨的實例;2、實例需要在多個方法中共享,但不希望被多線程共享。
1、存儲用戶Session(不同線程獲取到的用戶信息不一樣)
2、數據庫連接,處理數據庫事務
3、數據跨層傳遞
4、Spring使用ThreadLocal解決線程安全問題
五、ThreadLocal內存泄露原因
Entry將ThreadLocal作為Key,值作為value保存,它繼承自WeakReference,注意構造函數里的第一行代碼super(k),這意味著ThreadLocal對象是一個「弱引用」。
主要兩個原因:1、沒有手動刪除這個Entry;2、CurrentThread 當前線程依然運行
解決方案:1、只要在使用完下ThreadLocal,調用remove方法刪除對應的Entry,就能避免內存泄漏。
2、由于ThreadLocalMap 是 Thread 的一個屬性,被當前線程所引用,所以ThreadLocalMap的生命周期跟 Thread 一樣長。如果threadlocal變量被回收,那么當前線程的threadlocal 變量副本指向的就是key=null,也即entry(null,value),那這個entry對應的value永遠無法訪問到。如果ThreadLocal場景采用線程池,這樣就可能導致非常多的entry(null,value)出現,從而導致內存泄露。
綜上, ThreadLocal 內存泄漏的根源是:如果沒有手動刪除(remove()方法)對應 key 就會導致entry(null,value)的對象越來越多,從而導致內存泄漏。
六、如何正確的使用ThreadLocal
1、將ThreadLocal變量定義成private static的,這樣的話ThreadLocal的生命周期就更長,由于一直存在ThreadLocal的強引用,所以ThreadLocal也就不會被回收,也就能保證任何時候都能根據ThreadLocal的弱引用訪問到Entry的value值,然后remove它,防止內存泄露
2、每次使用完ThreadLocal,都調用它的remove()方法,清除數據。
七、ThreadLocal為什么不將key設置為強引用
如果key設計成強引用且沒有手動remove(),ThreadLocal ref被回收了,但是因為threadLocalMap的Entry強引用了threadLocal(key就是threadLocal), 造成ThreadLocal無法被回收。
當前線程始終有強引用鏈CurrentThread Ref → CurrentThread →Map(ThreadLocalMap)-> entry,Entry就不會被回收( Entry中包括了ThreadLocal實例和value),導致Entry內存泄漏,也就是說ThreadLocalMap中的key使用了強引用是無法完全避免內存泄漏的
弱引用比強引用可以多一層保障弱引用的 ThreadLocal 會被回收,對應value在下一次 ThreadLocaI 調用 get()/set()/remove() 中的任一方法的時候會被清除,從而避免內存泄漏。
總結
本文講解ThreadLocal是什么、ThreadLocal的數據結構以及ThreadLocal源碼set()/get()/remove()解析,ThreadLocal使用場景,如何正確的使用ThreadLocal,ThreadLocal內存泄露原因。