不規(guī)范使用ThreadLocal導(dǎo)致bug分析解決
因?yàn)榫€程重用導(dǎo)致的信息錯亂的bug
ThreadLocal一般用于線程間的數(shù)據(jù)隔離,通過將數(shù)據(jù)緩存在ThreadLocal中,可以極大的提升性能。但是,如果錯誤的使用Threadlocal,可能會引起不可預(yù)期的bug,以及造成內(nèi)存泄露。
有時我們會在一個接口中緩存某些數(shù)據(jù)到ThreadLocal中,但是我們要意識到,處理請求的這些線程是由tomcat提供的,而tomcat提供的線程都是配置在一個線程池中的。
也就是說,線程是可能被重用的,如果線程一旦被重用,而ThreadLocal的數(shù)據(jù)沒有及時重置,就會導(dǎo)致數(shù)據(jù)被混亂使用。
以下方的接口為例,先獲取當(dāng)前線程中保存的數(shù)據(jù)信息,將參數(shù)中的name保存到ThreadLocal中以后,再獲取一次。
@GetMapping(value = "/threadLocal")
public ResponseEntity<Object> threadLocal(String name) {
String before = Thread.currentThread().getName() + ":" + threadLocal.get();
//先獲取值,理論上應(yīng)該是null
System.out.println("before:" + before);
threadLocal.set(name);
String after = Thread.currentThread().getName() + ":" + threadLocal.get();
//設(shè)置完參數(shù)值再獲取一次
System.out.println("after:" + after);
return ResponseEntity.ok().build();
}
為了盡快復(fù)現(xiàn)線程重用導(dǎo)致的問題,我們將servlet.tomcat.threads.max設(shè)置為1,這樣每次請求使用的都是同一個線程。

第一次請求接口,數(shù)據(jù)看起來很正常:

但是第二次請求接口時,可以看到線程仍然是http-nio-8080-exec-1,但是before卻打印出了第一次請求的參數(shù)test。

這就是因?yàn)闆]有及時重置ThreadLocal導(dǎo)致的數(shù)據(jù)錯誤。
正確使用的姿勢
修正的辦法就是處理完接口之后要及時清理ThreadLocal。
@GetMapping(value = "/threadLocal")
public ResponseEntity<Object> threadLocal(String name) {
try {
String before = Thread.currentThread().getName() + ":" + threadLocal.get();
//先獲取值,理論上應(yīng)該是null
System.out.println("before:" + before);
threadLocal.set(name);
String after = Thread.currentThread().getName() + ":" + threadLocal.get();
//設(shè)置完參數(shù)值再獲取一次
System.out.println("after:" + after);
} finally {
//清理數(shù)據(jù)
threadLocal.remove();
}
return ResponseEntity.ok().build();
}
更優(yōu)雅的處理方式
可能也有的朋友會說,每次都要使用try finally處理線程數(shù)據(jù),未免也太麻煩了。其實(shí),我們可以使用攔截器或者過濾器自動幫我們完成數(shù)據(jù)的初始化以及清理工作。

最后
我們在寫業(yè)務(wù)代碼時,正確的理解線程的全生命周期以及執(zhí)行原理,對我們提升代碼的健壯性其實(shí)很有幫助。有時我們覺得底層原理很枯燥乏味,開發(fā)業(yè)務(wù)就是寫增刪改查,多線程用的也很少,但我們只是沒有意識到,我們的代碼一直跑在tomcat提供的線程池中,本身就是一個多線程的環(huán)境。
除了tomcat的線程池,我們自定義的線程池其實(shí)也會有這個問題,大家可以看看自己的業(yè)務(wù)代碼是否踩過這個坑。
以上就是不規(guī)范使用ThreadLocal導(dǎo)致bug分析解決的詳細(xì)內(nèi)容,更多關(guān)于ThreadLocal bug解決分析的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
如何利用Java獲取當(dāng)天的開始和結(jié)束時間
這篇文章主要介紹了如何使用Java?8的LocalDate和LocalDateTime類獲取指定日期的開始和結(jié)束時間,展示了如何通過這些類進(jìn)行日期和時間的處理,從而簡化了日期時間操作,需要的朋友可以參考下2025-02-02
詳解如何更改SpringBoot TomCat運(yùn)行方式
這篇文章主要介紹了詳解如何更改SpringBoot TomCat運(yùn)行方式,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04
IntelliJ IDEA 安裝 Grep Console插件 自定義控制臺輸出多顏色格式功能
由于Intellij idea不支持顯示ascii顏色,grep-console插件能很好的解決這個問題,下面就以開發(fā)JavaEE項(xiàng)目中,結(jié)合Log4j配置多顏色日志輸出功能,感興趣的朋友一起看看吧2020-05-05
Java PriorityQueue數(shù)據(jù)結(jié)構(gòu)接口原理及用法
這篇文章主要介紹了Java PriorityQueue數(shù)據(jù)結(jié)構(gòu)接口原理及用法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-10-10
IDEA2020.2.3中創(chuàng)建JavaWeb工程的完整步驟記錄
這篇文章主要給大家介紹了關(guān)于IDEA2020.2.3中創(chuàng)建JavaWeb工程的完整步驟,文中通過圖文介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12
Mybatis中使用萬能的Map傳參實(shí)現(xiàn)
在編程中,有可能遇到我們的實(shí)體類或者數(shù)據(jù)庫中表的字段或參數(shù)過多的情況,那這時候用Map傳參是比較理想的選擇,本文就詳細(xì)的介紹一下如何使用,感興趣的可以了解下2021-07-07
使用JWT作為Spring?Security?OAuth2的token存儲問題
這篇文章主要介紹了使用JWT作為Spring?Security?OAuth2的token存儲,大家經(jīng)常使用的方法有兩種一種是使用JWT作為Token傳遞,一種是使用Redis存儲Token,資源服務(wù)器本地訪問Redis校驗(yàn)Token,需要的朋友可以參考下2021-12-12
MyBatis-Plus模糊查詢特殊字符串轉(zhuǎn)義的實(shí)現(xiàn)
使用MyBatis中的模糊查詢時,當(dāng)查詢關(guān)鍵字中包括有_、\、%時,查詢關(guān)鍵字失效,本文主要介紹了MyBatis-Plus模糊查詢特殊字符串轉(zhuǎn)義的實(shí)現(xiàn),感興趣的可以了解一下2024-06-06

