ThreadLocal原理及使用

一、引言

在Java多線程編程中,ThreadLocal是一個非常有用的工具,它提供了一種將對象與線程關聯起來的機制,使得每個線程都可以擁有自己獨立的對象副本,從而避免了線程安全問題。然而,使用不當會導致內存泄漏問題。

二、ThreadLocal介紹

ThreadLocal是一個線程本地變量(與其說是線程本地變量,不如說是線程局部變量),它為每個線程提供了一個獨立的副本,每個線程都可以獨立地改變自己的副本,而不會影響其他線程的副本。ThreadLocal通常用于解決線程安全問題,例如在多線程環境下共享對象時,可以使用ThreadLocal來保存每個線程獨立的對象副本,從而避免了同步操作。下面筆者提供一個代碼案例來說明它的用法。

package com.execute.batch.executebatch;import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;/*** 日期工具類* @author hulei*/
public class DateUtil {private static final SimpleDateFormat simpleDateFormat =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");public static Date parse(String dateString) {Date date = null;try {date = simpleDateFormat.parse(dateString);} catch (ParseException e) {e.printStackTrace();}return date;}
}

上面是一個日期工具類,內部定義了一個日期格式轉換方法parse(),還有一個日期格式轉換器SimpleDateFormat類。

多線程測試代碼如下

package com.execute.batch.executebatch;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;/*** @author hulei* @date  2024/5/23 15:44*/public class ThreadLocalTest {public static void main(String[] args) {ExecutorService executorService = Executors.newFixedThreadPool(10);for (int i = 0; i < 10; i++) {executorService.execute(()->{System.out.println(DateUtil.parse("2024-05-23 16:34:30"));});}executorService.shutdown();}
}

測試結果報錯
在這里插入圖片描述

把工具類的

    private static final SimpleDateFormat simpleDateFormat =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

替換成如下寫法,用ThreadLocal包起來

    private static final ThreadLocal<SimpleDateFormat> dateFormatThreadLocal =ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

工具類變成如下

package com.execute.batch.executebatch;import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/*** 日期工具類* @author hulei*/
public class DateUtil {private static final ThreadLocal<SimpleDateFormat> dateFormatThreadLocal =ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));public static Date parse(String dateString) {Date date = null;try {date = dateFormatThreadLocal.get().parse(dateString);} catch (ParseException e) {e.printStackTrace();}return date;}
}

測試發現不報錯了

package com.execute.batch.executebatch;import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/*** 日期工具類* @author hulei*/
public class DateUtil {private static final ThreadLocal<SimpleDateFormat> dateFormatThreadLocal =ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));public static Date parse(String dateString) {Date date = null;try {date = dateFormatThreadLocal.get().parse(dateString);} catch (ParseException e) {e.printStackTrace();}return date;}
}

剛才第一次測試報錯,是因為SimpleDateFormat不是線程安全的類,SimpleDateFormat 不是線程安全的主要原因在于以下幾個方面:

  • 內部狀態共享:SimpleDateFormat 內部維護了一些狀態,如日期字段的解析和格式化信息。這些狀態在解析或格式化日期時可能會被修改。當多個線程同時訪問一個實例時,如果沒有適當的同步控制,這些狀態的修改可能會發生沖突,導致不一致的結果。

  • 可變性:SimpleDateFormat 實例是可以修改的。比如,可以通過調用 applyPattern() 方法來改變其格式模式,這會影響實例的狀態。如果多個線程同時修改同一個實例,可能會出現競態條件。

  • 緩存行為:SimpleDateFormat 在解析日期時,可能會緩存一些日期字段的解析結果,這些緩存是基于實例的。如果多個線程同時訪問,可能會導致緩存的數據不準確或丟失。

  • 線程本地副本:在某些情況下,SimpleDateFormat 實例可能需要使用線程本地副本來提高性能,但Java的標準實現并未內置這樣的機制,所以開發者需要手動處理線程安全問題。

為了避免這些問題,有幾種常見的解決方案:

  • 線程局部實例:為每個線程創建單獨的 SimpleDateFormat 實例,避免共享。

  • 同步訪問:如果必須共享實例,可以在訪問時使用 synchronized 關鍵字或 java.util.concurrent.locks.Lock 進行同步。

  • 使用不可變的 DateTimeFormatter:Java 8及更高版本提供了 java.time.format.DateTimeFormatter 類,它是線程安全的,可以替代 SimpleDateFormat。

在多線程環境中,使用 ThreadLocal 是一個好的選擇,因為它可以確保每個線程擁有自己SimpleDateFormat 實例,從而消除線程安全問題。

三、內存泄露問題

雖然ThreadLocal提供了一種便捷的線程封閉機制,但是如果使用不當會導致內存泄漏問題。ThreadLocal的內存泄漏問題主要表現在以下兩個方面:

  1. 線程結束后沒有手動清理
    當一個線程結束后,它所持有的ThreadLocal變量并不會立即釋放,如果沒有手動調用remove()方法清理ThreadLocal變量,那么這些變量會一直保留在內存中,直到線程池被銷毀或者應用程序退出。

  2. ThreadLocal變量被弱引用持有
    ThreadLocal內部通過一個ThreadLocalMap來存儲線程獨立的變量副本,而ThreadLocalMap中的Entry是由ThreadLocal的弱引用持有的。如果一個ThreadLocal沒有被外部強引用持有,那么在垃圾回收時,ThreadLocal對象會被回收,但是對應的Entry并不會被自動清理,這樣就會導致內存泄漏問題。

四、避免內存泄漏

為了避免ThreadLocal的內存泄漏問題,我們可以采取以下幾種解決方案:

及時清理ThreadLocal變量

在使用完ThreadLocal變量后,應該及時調用remove()方法清理ThreadLocal變量,以便釋放資源。

ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal.set("value");
// 使用完畢后清理ThreadLocal變量
threadLocal.remove();

日期轉換工具類代碼可以加入以下語句清理ThreadLocal變量
在這里插入圖片描述

使用ThreadLocal的弱引用

為了避免ThreadLocal對象被強引用持有導致的內存泄漏問題,可以將ThreadLocal聲明為靜態內部類,以使得ThreadLocal對象的生命周期比較長,從而避免了被短生命周期的線程持有。意思是生命為靜態內部變量,大致如下:

public class MyThreadLocal {private static final ThreadLocal<Object> threadLocal = new ThreadLocal<>();// 省略其他代碼
}

使用InheritableThreadLocal

InheritableThreadLocal是ThreadLocal的一個子類,它可以讓子線程從父線程中繼承ThreadLocal變量,但是使用InheritableThreadLocal也會增加內存泄漏的風險,因此需要謹慎使用。

public class MyThreadLocal {private static final ThreadLocal<Object> threadLocal = new InheritableThreadLocal<>();// 省略其他代碼
}

注意:實際java8以后的版本,ThreadLocal的實現包含了一個弱引用機制,當線程結束時,即使未手動調用remove(),與線程相關的ThreadLocalMap.Entry也會有機會被垃圾回收器回收,從而減少了內存泄漏的風險。但這種機制并不能完全排除內存泄漏,特別是在長期運行的線程或線程池中,如果ThreadLocal的引用沒有被及時清理,仍然可能導致大量無用對象占據內存空間。所以仍然建議手動釋放掉ThreadLocal變量。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/14090.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/14090.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/14090.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

go 微服務框架kratos錯誤處理的使用方法及原理探究

通過go語言原生http中響應錯誤的實現方法&#xff0c;逐步了解和使用微服務框架 kratos 的錯誤處理方式&#xff0c;以及探究其實現原理。 一、go原生http響應錯誤信息的處理方法 處理方法&#xff1a; ①定義返回錯誤信息的結構體 ErrorResponse // 定義http返回錯誤信息的…

無人機飛手前途分析

無人機飛手的前途充滿了各種可能性和挑戰&#xff0c;這主要得益于無人機技術的快速發展和廣泛應用。以下是對無人機飛手前途的一些分析&#xff1a; 1. 技術發展與需求增長&#xff1a;隨著無人機技術的不斷進步&#xff0c;其應用場景也在持續擴大。從地理測繪、巡檢、農林植…

利用阿里OSS服務給文件設置過期刪除--簡單版

在云存儲廣泛應用的今天&#xff0c;阿里云的Object Storage Service&#xff08;OSS&#xff09;以其高度可擴展性、安全性和成本效益&#xff0c;成為了眾多企業和開發者存儲海量數據的首選方案。隨著數據量的不斷膨脹&#xff0c;高效的數據管理和成本控制變得尤為重要。其中…

IT學習筆記--Kafka

Kafka概述: 定義: Kafka是一個分布式的基于發布/訂閱模式的消息隊列&#xff0c;主要應用于大數據實時處理領域。 消息隊列消息隊列的兩種模式: 點對點模式: 消息生產者生產消息發送到Queue中&#xff0c;然后消息消費者從Queue中取出并且消費消息。 消息被消費以后&#…

Linux中解決普通用戶使用不了sudo問題

目錄 sudo的使用場景sudo使用不了的原因解決方法 sudo的使用場景 之前我們介紹了文件的權限問題 如果一個普通用戶想去執行一個它命令之外的權限&#xff0c;只能使用sudo 比如普通用戶使用yum去安裝軟件&#xff0c;需要sudo yum xxxx sudo使用不了的原因 這里我們用普通用戶…

小恐龍跳一跳源碼

小恐龍跳一跳源碼是前兩年就火爆過一次的小游戲源碼&#xff0c;不知怎么了今年有火爆了&#xff0c;所以今天就吧這個源碼分享出來了&#xff01;有喜歡的直接下載就行&#xff0c;可以本地單機直接點擊index.html進行運行&#xff0c;又或者放在虛擬機或者服務器上與朋友進行…

python 獲取視頻的時長

以下是幾種獲取視頻時長的實現方法&#xff1a; 方法一&#xff1a;使用moviepy庫 from moviepy.editor import VideoFileClipdef get_video_duration(file_path):video VideoFileClip(file_path)duration video.durationvideo.close()return duration 方法二&#xff1a;…

SAP-FICO-憑證編號控制

成本憑證編號KANK 如果自己的公司下沒有&#xff0c;直接復制系統原有的就可以。使用系統默認即可。 如果不維護 會報錯“CO-憑證編號分配對于成本控制范圍****中的商業事務COIN無效” 財務憑證編號FBN1 可以用OBH2批量復制編號范圍。 物料賬期MMPV 財務賬期OB52

python使用base加密解密

原理 base編碼是一種加密解密措施&#xff0c;目前常用的有base16、base32和base64。其大致原理比較簡單。 以base64為例&#xff0c;base64加密后共有64中字符。其加密過程是編碼后將每3個字節作為一組&#xff0c;這樣每組就有3*824位。將每6位作為一個單位進行編碼&#xf…

1個逗號,提升Python代碼質量

有些時候&#xff0c;我們會在Python代碼中看到列表或其他科迭代對象的結尾會存在一個逗號&#xff1a; 而且編輯器和解釋器都容許這種逗號的存在&#xff0c;它就叫作拖尾逗號。 通常是為了在頻繁地增減數組元素的時候同時保證語法的正確&#xff0c;且拖尾逗號不占用數組的長…

MySQL 主備環境搭建 docker

MySQL 主備環境搭建 docker 拉取docker鏡像 sudo docker pull mysql:8.0 啟動容器 docker run -p 3339:3306 --name mysql-master -e MYSQL_ROOT_PASSWORD123456 -d mysql:8.0docker run -p 3340:3306 --name mysql-slave -e MYSQL_ROOT_PASSWORD123456 -d mysql:8.0配置 M…

第四十二天 | 背包問題理論

二維&#xff1a; 1.dp[i][j] 表示從下標為[0-i]的物品里任意取&#xff0c;放進容量為j的背包&#xff0c;價值總和最大是多少。 2.遞歸公式&#xff1a; dp[i][j] max(dp[i - 1][j], dp[i - 1][j - weight[i]] value[i]); 3.初始化&#xff1a; 首先從dp[i][j]的定義出發…

基于xilinx fpga RFSOC系列的Ultrascale+ RF Data Converter ip詳解說明

目錄 1 概述2 IP功能2.1 ADC性能2.2 DAC性能3 IP端口4 代碼框架4.1 ADC功能框圖4.2 DAC功能框圖5 收發數據時序5.1 ADC數據格式5.2 DAC數據格式6 時鐘配置6.1 ADC/DAC參考時鐘7 數據格式配置模式7.1 ADC的配置模式7.1.1 Real -> real;7.1.2 Real ->IQ;7.1.3 IQ -> IQ;…

【設計模式】JAVA Design Patterns——Bridge(橋接模式)

&#x1f50d;目的 將抽象與其實現分離&#xff0c;以便二者可以獨立變化。 &#x1f50d;解釋 真實世界例子 考慮一下你擁有一種具有不同附魔的武器&#xff0c;并且應該允許將具有不同附魔的不同武器混合使用。 你會怎么做&#xff1f; 為每個附魔創建每種武器的多個副本&…

當代人工智能三教父——深度學習三巨頭

文章目錄 引言 人物介紹 突出貢獻 專業名詞解釋 引言 今天下午閑來無事翻閱了一下csdn首頁的頭條文章——《27 歲天才創始人 Joel Hellermark 分享了自己和“AI 教父” Geoffery Hinton 的最新采訪》 感覺挺有意思&#xff0c;就從頭到尾的看了一遍&#xff0c;里面有很多…

pyqt5與yolov5進行視頻檢測(一)——登錄操作

項目效果展示 一、登錄界面 二、主界面 目前在更新中。。。 一、設計 二、登錄代碼 注意&#xff1a;下面會導入主界面的包&#xff0c;圖片資源自己設計一下&#xff0c;密碼保存時沒設計加密&#xff0c;需要自行設計 main_window主界面下文會設計from main_window impor…

無線通信的穿墻能力主要取決于哪些指標

無線通信的穿墻能力是指無線信號在穿越建筑物墻壁時&#xff0c;其信號衰減程度以及能否維持足夠強度以進行穩定通信的能力。穿墻能力的好壞直接影響到無線通信在室內環境中的覆蓋范圍和使用體驗。 一、無線信號的頻率 無線信號的頻率是影響穿墻能力的重要因素之一。一般來說…

工行音視頻服務平臺建設與應用經驗

近些年來&#xff0c;伴隨著技術能力的積累突破&#xff0c;音視頻服務開始蓬勃生長走進千家萬戶&#xff0c;使用遠程視頻通話、觀看各類視頻直播逐漸成為人們的日常&#xff0c;而金融服務作為社會生活的重要組成部分&#xff0c;自然需要積極擁抱應用新技術。 如今&#xff…

怎么知道Python包的依賴項

要查看Python包的依賴項,有幾種方法可以做到這一點: 使用pip: pip是Python的包管理器,它允許你安裝和管理Python庫。要查看一個包的依賴關系,你可以使用pip show命令加上包名,但請注意,直接用pip show并不直接列出依賴項,它提供包的詳細信息,包括它的安裝路徑。為了查看…

Kubernetes Deployment 之擴縮容與滾動更新

Kubernetes Deployment 之擴縮容與滾動更新 Deployment 擴縮容 擴縮容非常簡單&#xff0c;我們可以直接調整 replica 副本數目&#xff0c;然后 kubectl apply指定進行動態更新。下面將nginx-deployment動態改為 1 個 Pod 和 3 個 Pod 的操作 apiVersion: apps/v1 kind: De…