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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...