ThreadLocal源碼解析及實戰應用

来源:https://www.cnblogs.com/Jcloud/archive/2023/01/09/17036499.html
-Advertisement-
Play Games

ThreadLocal是一個關於創建線程局部變數的類。 通常情況下,我們創建的變數是可以被任何一個線程訪問並修改的。而使用ThreadLocal創建的變數只能被當前線程訪問,其他線程則無法訪問和修改。ThreadLocal在設計之初就是為解決併發問題而提供一種方案,每個線程維護一份自己的數據,達到線... ...


作者:京東物流 閆鵬勃

1 什麼是ThreadLocal?

ThreadLocal是一個關於創建線程局部變數的類。
通常情況下,我們創建的變數是可以被任何一個線程訪問並修改的。而使用ThreadLocal創建的變數只能被當前線程訪問,其他線程則無法訪問和修改。ThreadLocal在設計之初就是為解決併發問題而提供一種方案,每個線程維護一份自己的數據,達到線程隔離的效果。

2 有什麼作用?

2.1 set once,get everywhere

在現在的系統設計中,前後端分離已基本成為常態,分離之後如何獲取用戶信息就成了一件麻煩事,通常在用戶登錄後, 用戶信息會保存在Session或者Token中。這個時候,我們如果使用常規的手段去獲取用戶信息會很費勁,拿Session來說,我們要在介面參數中加上HttpServletRequest對象,然後調用 getSession方法,且每一個需要用戶信息的介面都要加上這個參數,才能獲取Session,這樣實現就很麻煩了。

在實際的系統設計中,我們肯定不會採用上面所說的這種方式,而是使用ThreadLocal,我們會選擇在攔截器的業務中, 獲取到保存的用戶信息,然後存入ThreadLocal,那麼當前線程在任何地方如果需要拿到用戶信息都可以使用ThreadLocal的get()方法 (非同步程式中ThreadLocal是不可靠的)

2.2 線程安全,空間換時間

在Spring的Web項目中,我們通常會將業務分為Controller層,Service層,Dao層, 我們都知道@Autowired註解預設使用單例模式,那麼不同請求線程進來之後,由於Dao層使用單例,那麼負責資料庫連接的Connection也只有一個, 如果每個請求線程都去連接資料庫,那麼就會造成線程不安全的問題,Spring是如何解決這個問題的呢?

在Spring項目中Dao層中裝配的Connection肯定是線程安全的,其解決方案就是採用ThreadLocal方法,當每個請求線程使用Connection的時候, 都會從ThreadLocal獲取一次,如果為null,說明沒有進行過資料庫連接,連接後存入ThreadLocal中,如此一來,每一個請求線程都保存有一份 自己的Connection。於是便解決了線程安全問題

3 ThreadLocal實戰應用

3.1 ehr中的使用

在登錄攔截器中將用戶信息寫入,後續使用時方便取值

3.2 分頁插件PageHelper中的應用

3.3 AopContext

4 源碼解讀

你是否有這樣的疑惑?為什麼可以直接拿到?對象存放在哪裡?存在什麼問題?

4.1 get方法

在 get() 方法中也會獲取到當前線程的 ThreadLocalMap,如果 ThreadLocalMap 不為 null,則把獲取 key 為當前 ThreadLocal 的值;否則調用 setInitialValue() 方法返回初始值,並保存到新創建的 ThreadLocalMap 中。

4.2 set方法

調用set時,直接調用set(T value) 方法中,首先獲取當前線程,然後在獲取到當前線程的 ThreadLocalMap,如果 ThreadLocalMap 不為 null,則將 value 保存到 ThreadLocalMap 中,並用當前 ThreadLocal 作為 key;否則創建一個 ThreadLocalMap 並給到當前線程,然後保存 value。

ThreadLocalMap 相當於一個 HashMap,是真正保存值的地方
map的set,如果map為空,則創建一個

4.3 initialValue() 方法

initialValue() 是 ThreadLocal 的初始值,預設返回 null,子類可以重寫改方法,用於設置 ThreadLocal 的初始值。

4.4 remove() 方法

ThreadLocal 還有一個 remove() 方法,用來移除當前 ThreadLocal 對應的值。同樣也是同過當前線程的 ThreadLocalMap 來移除相應的值。

getMap拿到了什麼?
在 set,get,initialValue 和 remove 方法中都會獲取到當前線程,然後通過當前線程獲取到 ThreadLocalMap,如果 ThreadLocalMap 為 null,則會創建一個 ThreadLocalMap,並給到當前線程

此處t是Thread,直接可以“點”拿到這個map
每個Thread對象內部都維護了一個ThreadLocalMap這樣一個ThreadLocal的Map,可以存放若幹個ThreadLocal

在使用 ThreadLocal 類型變數進行相關操作時,都會通過當前線程獲取到 ThreadLocalMap 來完成操作。每個線程的 ThreadLocalMap 是屬於線程自己的,ThreadLocalMap 中維護的值也是屬於線程自己的。這就保證了 ThreadLocal 類型的變數在每個線程中是獨立的,在多線程環境下不會相互影響。

5 使用註意事項

1)有可能導致記憶體泄漏,使用完畢後,需要remove

在 ThreadLocalMap 的 set(),get() 和 remove() 方法中,都有清除無效 Entry 的操作,這樣做是為了降低記憶體泄漏發生的可能。
Entry 中的 key 使用了弱引用的方式,這樣做是為了降低記憶體泄漏發生的概率,但不能完全避免記憶體泄漏。

假設 Entry 的 key 沒有使用弱引用的方式,而是使用了強引用:由於 ThreadLocalMap 的生命周期和當前線程一樣長,那麼當引用 ThreadLocal 的對象被回收後,由於 ThreadLocalMap 還持有 ThreadLocal 和對應 value 的強引用,ThreadLocal 和對應的 value 是不會被回收的,這就導致了記憶體泄漏。所以 Entry 以弱引用的方式避免了 ThreadLocal 沒有被回收而導致的記憶體泄漏,但是此時 value 仍然是無法回收的,依然會導致記憶體泄漏。

ThreadLocalMap 已經考慮到這種情況,並且有一些防護措施:在調用 ThreadLocal 的 get(),set() 和 remove() 的時候都會清除當前線程 ThreadLocalMap 中所有 key 為 null 的 value。這樣可以降低記憶體泄漏發生的概率。所以我們在使用 ThreadLocal 的時候,每次用完 ThreadLocal 都調用 remove() 方法,清除數據,防止記憶體泄漏。

2)使用線程池時,父子線程傳遞慎用,因為初始化時機為線程創建時

3)針對2有什麼方案可以解決?
TransmittableThreadLocal
源碼地址: https://github.com/alibaba/transmittable-thread-local
詳解:https://www.jianshu.com/p/e0774f965aa3


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Redis 數據結構-簡單動態字元串 無邊落木蕭蕭下,不盡長江滾滾來。 1、簡介 Redis 之所以快主要得益於它的數據結構、操作記憶體資料庫、單線程和多路 I/O 復用模型,進一步窺探下它常見的五種基本數據的底層數據結構。 Redis 常見數據類型對應的的底層數據結構。 String:簡單動態字元串 ...
  • 前言 今天給大家介紹的是Python爬蟲批量下載評書音頻並保存本地,在這裡給需要的小伙伴們代碼,並且給出一點小心得。 首先是爬取之前應該儘可能偽裝成瀏覽器而不被識別出來是爬蟲,基本的是加請求頭,但是這樣的純文本數據爬取的人會很多,所以我們需要考慮更換代理IP和隨機更換請求頭的方式來對評書精選音頻進行 ...
  • 互聯網世界里最流行的開源關係型資料庫之一就是MySQL/MariaDB了,由於高度的相似,故而直接使用mysql統一指稱。 ...
  • 項目地址:https://github.com/pikeduo/TXTReader PyQt5中文手冊:https://maicss.gitbook.io/pyqt-chinese-tutoral/pyqt5/ QtDesigner學習地址:https://youcans.blog.csdn.net ...
  • 14. 最長公共首碼 題目描述 編寫一個函數來查找字元串數組中的最長公共首碼。 如果不存在公共首碼,返回空字元串 ""。 方法 暴力演算法 先判斷字元串數組是否有為空,為空直接返回空 令第一個字元串作為基準進行比較 設置一個長度,作為最後最長公共首碼的長度 迴圈判斷,選取最小長度 代碼 package ...
  • 2023-01-09 一、在IDEA中創建Maven版的web工程 (1)步驟: ①創建一個maven模塊,命名為“maven_web_end”,之後需要創建web工程的目錄。在“maven_web_end.src.main”下創建“webapp”文件夾(命名必須為webapp,否則識別不了);在“ ...
  • 作者:小牛呼嚕嚕 | https://xiaoniuhululu.com 電腦內功、JAVA底層、面試相關資料等更多精彩文章在公眾號「小牛呼嚕嚕 」 大家好,我是呼嚕嚕,這次我們一起來看看Java記憶體區域,本文 基於HotSpot 虛擬機,JDK8, 乾貨滿滿 前言 Java 記憶體區域, 也叫運行 ...
  • Excelize 是 Go 語言編寫的用於操作 Office Excel 文檔基礎庫,支持 XLAM / XLSM / XLSX / XLTM / XLTX 等多種文檔格式。2023年1月9日,社區正式發佈了 2.7.0 版本,該版本包含了多項新增功能、錯誤修複和相容性提升優化。 ...
一周排行
    -Advertisement-
    Play Games
  • GoF之工廠模式 @目錄GoF之工廠模式每博一文案1. 簡單說明“23種設計模式”1.2 介紹工廠模式的三種形態1.3 簡單工廠模式(靜態工廠模式)1.3.1 簡單工廠模式的優缺點:1.4 工廠方法模式1.4.1 工廠方法模式的優缺點:1.5 抽象工廠模式1.6 抽象工廠模式的優缺點:2. 總結:3 ...
  • 新改進提供的Taurus Rpc 功能,可以簡化微服務間的調用,同時可以不用再手動輸出模塊名稱,或調用路徑,包括負載均衡,這一切,由框架實現並提供了。新的Taurus Rpc 功能,將使得服務間的調用,更加輕鬆、簡約、高效。 ...
  • 本章將和大家分享ES的數據同步方案和ES集群相關知識。廢話不多說,下麵我們直接進入主題。 一、ES數據同步 1、數據同步問題 Elasticsearch中的酒店數據來自於mysql資料庫,因此mysql數據發生改變時,Elasticsearch也必須跟著改變,這個就是Elasticsearch與my ...
  • 引言 在我們之前的文章中介紹過使用Bogus生成模擬測試數據,今天來講解一下功能更加強大自動生成測試數據的工具的庫"AutoFixture"。 什麼是AutoFixture? AutoFixture 是一個針對 .NET 的開源庫,旨在最大程度地減少單元測試中的“安排(Arrange)”階段,以提高 ...
  • 經過前面幾個部分學習,相信學過的同學已經能夠掌握 .NET Emit 這種中間語言,並能使得它來編寫一些應用,以提高程式的性能。隨著 IL 指令篇的結束,本系列也已經接近尾聲,在這接近結束的最後,會提供幾個可供直接使用的示例,以供大伙分析或使用在項目中。 ...
  • 當從不同來源導入Excel數據時,可能存在重覆的記錄。為了確保數據的準確性,通常需要刪除這些重覆的行。手動查找並刪除可能會非常耗費時間,而通過編程腳本則可以實現在短時間內處理大量數據。本文將提供一個使用C# 快速查找並刪除Excel重覆項的免費解決方案。 以下是實現步驟: 1. 首先安裝免費.NET ...
  • C++ 異常處理 C++ 異常處理機制允許程式在運行時處理錯誤或意外情況。它提供了捕獲和處理錯誤的一種結構化方式,使程式更加健壯和可靠。 異常處理的基本概念: 異常: 程式在運行時發生的錯誤或意外情況。 拋出異常: 使用 throw 關鍵字將異常傳遞給調用堆棧。 捕獲異常: 使用 try-catch ...
  • 優秀且經驗豐富的Java開發人員的特征之一是對API的廣泛瞭解,包括JDK和第三方庫。 我花了很多時間來學習API,尤其是在閱讀了Effective Java 3rd Edition之後 ,Joshua Bloch建議在Java 3rd Edition中使用現有的API進行開發,而不是為常見的東西編 ...
  • 框架 · 使用laravel框架,原因:tp的框架路由和orm沒有laravel好用 · 使用強制路由,方便介面多時,分多版本,分文件夾等操作 介面 · 介面開發註意欄位類型,欄位是int,查詢成功失敗都要返回int(對接java等強類型語言方便) · 查詢介面用GET、其他用POST 代碼 · 所 ...
  • 正文 下午找企業的人去鎮上做貸後。 車上聽同事跟那個司機對罵,火星子都快出來了。司機跟那同事更熟一些,連我在內一共就三個人,同事那一手指桑罵槐給我都聽愣了。司機也是老社會人了,馬上聽出來了,為那個無辜的企業經辦人辯護,實際上是為自己辯護。 “這個事情你不能怪企業。”“但他們總不能讓銀行的人全權負責, ...