五個Java中線程池使用不當?shù)谋芸又改?/h1>
更新時間:2024年02月05日 10:42:49 作者:waynaqua
線程池是?Java?多線程編程中的一個重要概念,它可以有效地管理和復用線程資源,提高系統(tǒng)的性能和穩(wěn)定性,本文將介紹線程池使用不當?shù)奈鍌€坑,以及如何避免和解決它們,希望對大家有所幫助
線程池是 Java 多線程編程中的一個重要概念,它可以有效地管理和復用線程資源,提高系統(tǒng)的性能和穩(wěn)定性。但是線程池的使用也有一些注意事項和常見的錯誤,如果不小心,就可能會導致一些嚴重的問題,比如內(nèi)存泄漏、死鎖、性能下降等。
本文將介紹線程池使用不當?shù)奈鍌€坑,以及如何避免和解決它們,大綱如下,

坑一:線程池中異常消失
線程池執(zhí)行方法時要添加異常處理,這是一個老生常談的問題,可是直到最近我都有同事還在犯這個錯誤,所以我還是要講一下,不過我還提到了一種優(yōu)雅的線程池全局異常處理的方法,大家可以往下看。
問題原因
@Test
public void test() throws Exception {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
5,
10,
60,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100000));
Future<Integer> submit = threadPoolExecutor.execute(() -> {
int i = 1 / 0; // 發(fā)生異常
return i;
});
}
如上代碼,在線程池執(zhí)行任務時,沒有添加異常處理。導致任務內(nèi)部發(fā)生異常時,內(nèi)部錯誤無法被記錄下來。
解決方法
在線程池執(zhí)行任務方法內(nèi)添加 try/catch 處理,代碼如下,
@Test
public void test() throws Exception {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
5,
10,
60,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100000));
Future<Integer> submit = threadPoolExecutor.execute(() -> {
try {
int i = 1 / 0;
return i;
} catch (Exception e) {
log.error(e.getMessage(), e);
return null;
}
});
}
優(yōu)雅的進行線程池異常處理
當線程池調(diào)用任務方法很多時,那么每個線程池任務執(zhí)行的方法內(nèi)都要添加 try/catch 處理,這就不優(yōu)雅了,其實 ThreadPoolExecutor 線程池類支持傳入 ThreadFactory 參數(shù)用于自定義線程工廠,這樣我們在創(chuàng)建線程時,就可以指定 setUncaughtExceptionHandler 異常處理方法。
這樣就可以做到全局處理異常了,代碼如下,
ThreadFactory threadFactory = r -> {
Thread thread = new Thread(r);
thread.setUncaughtExceptionHandler((t, e) -> {
// 記錄線程異常
log.error(e.getMessage(), e);
});
return thread;
};
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
5,
10,
60,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100000));
threadPoolExecutor.execute(() -> {
log.info("---------------------");
int i = 1 / 0;
});
不過要注意的是上面 setUncaughtExceptionHandler 方法只能針對線程池的 execute 方法來全局處理異常。對于線程池的 submit 方法是無法處理的。
坑二:拒絕策略設置錯誤導致接口超時
在 Java 中,線程池拒絕策略可以說一個常見八股文問題。大家雖然都記住了線程池有四種決絕策略,可是實際代碼編寫中,我發(fā)現(xiàn)大多數(shù)人都只會用 CallerRunsPolicy 策略(由調(diào)用線程處理任務)。我吃過這個虧,因此也拿出來講講。
問題原因
曾經(jīng)有一個線上業(yè)務接口使用了線程池進行第三方接口調(diào)用,線程池配置里的拒絕策略采用的是 CallerRunsPolicy。示例代碼如下,
// 某個線上線程池配置如下
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
50, // 最小核心線程數(shù)
50, // 最大線程數(shù),當隊列滿時,能創(chuàng)建的最大線程數(shù)
60L, TimeUnit.SECONDS, // 空閑線程超過核心線程時,回收該線程的最大等待時間
new LinkedBlockingQueue<>(5000), // 阻塞隊列大小,當核心線程使用滿時,新的線程會放進隊列
new CustomizableThreadFactory("task"), // 自定義線程名
new ThreadPoolExecutor.CallerRunsPolicy() // 線程執(zhí)行的拒絕策略
);
threadPoolExecutor.execute(() -> {
// 調(diào)用第三方接口
...
});
在第三方接口異常的情況下,線程池任務調(diào)用第三方接口一直超時,導致核心線程數(shù)、最大線程數(shù)堆積被占滿、阻塞隊列也被占滿的情況下,也就會執(zhí)行拒絕策略,但是由于使用的是 CallerRunsPolicy 策略,導致線程任務直接由我們的業(yè)務線程來執(zhí)行。
因為第三方接口異常,所以業(yè)務線程執(zhí)行也會繼繼續(xù)超時,線上服務采用的 Tomcat 容器,最終也就導致 Tomcat 的最大線程數(shù)也被占滿,進而無法繼續(xù)向外提供服務。
解決方法
首先我們要考慮業(yè)務接口的可用性,就算線程池任務被丟棄,也不應該影響業(yè)務接口。
在業(yè)務接口穩(wěn)定性得到保證的情況下,在考慮到線程池任務的重要性,不是很重要的話,可以使用 DiscardPolicy 策略直接丟棄,要是很重要,可以考慮使用消息隊列來替換線程池。
坑三:重復創(chuàng)建線程池導致內(nèi)存溢出
不知道大家有沒有犯過這個問題,不過我確實犯過,歸根結底還是寫代碼前,沒有思考好業(yè)務邏輯,直接動手,寫一步算一步 。所以說寫代碼的前的一些邏輯梳理、拆分、代碼設計很重要。
問題原因
這個問題的原因很簡單,就是在一個方法內(nèi)重復創(chuàng)建了線程池,在執(zhí)行完之后卻沒有關閉。比較經(jīng)典的就是在定時任務內(nèi)使用線程池時有可能犯這個問題,示例代碼如下,
@XxlJob("test")
public void test() throws Exception {
// 某個線上線程池配置如下
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
50, // 最小核心線程數(shù)
50, // 最大線程數(shù),當隊列滿時,能創(chuàng)建的最大線程數(shù)
60L, TimeUnit.SECONDS, // 空閑線程超過核心線程時,回收該線程的最大等待時間
new LinkedBlockingQueue<>(5000), // 阻塞隊列大小,當核心線程使用滿時,新的線程會放進隊列
new CustomizableThreadFactory("task"), // 自定義線程名
new ThreadPoolExecutor.CallerRunsPolicy() // 線程執(zhí)行的拒絕策略
);
threadPoolExecutor.execute(() -> {
// 任務邏輯
...
});
}
當我們在定時任務中想使用線程池來縮短任務執(zhí)行時間時,千萬要注意別再任務內(nèi)創(chuàng)建了線程池,一旦犯了,基本都會在程序運行一段時間后發(fā)現(xiàn)程序突然間就掛了,留下了一堆內(nèi)存 dump 報錯的文件。
解決方法
使用線程池單例,切勿重復創(chuàng)建線程池。示例代碼如下,
// 某個線上線程池配置如下
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
50, // 最小核心線程數(shù)
50, // 最大線程數(shù),當隊列滿時,能創(chuàng)建的最大線程數(shù)
60L, TimeUnit.SECONDS, // 空閑線程超過核心線程時,回收該線程的最大等待時間
new LinkedBlockingQueue<>(5000), // 阻塞隊列大小,當核心線程使用滿時,新的線程會放進隊列
new CustomizableThreadFactory("task"), // 自定義線程名
new ThreadPoolExecutor.CallerRunsPolicy() // 線程執(zhí)行的拒絕策略
);
@XxlJob("test")
public void test() throws Exception {
threadPoolExecutor.execute(() -> {
// 任務邏輯
// ...
});
}
坑四:共用線程池執(zhí)行不同類型任務導致效率低下
有時候,我們可能會想要節(jié)省線程資源,把不同類型的任務都放到同一個線程池中執(zhí)行,比如主要的業(yè)務邏輯和次要的日志記錄、監(jiān)控等。這看起來很合理,但是實際上,這樣做可能會導致一個任務影響另一個任務,甚至導致死鎖的問題。
問題原因
問題的原因是,不同類型的任務可能有不同的執(zhí)行時間、優(yōu)先級、依賴關系等,如果放到同一個線程池中,就可能會出現(xiàn)以下幾種情況:
- 如果一個任務執(zhí)行時間過長,或者出現(xiàn)異常,那么它就會占用線程池中的一個線程,導致其他任務無法及時得到執(zhí)行,影響系統(tǒng)的吞吐量和響應時間。
- 如果一個任務的優(yōu)先級較低,或者不是很重要,那么它就可能搶占線程池中的一個線程,導致其他任務無法及時得到執(zhí)行,影響系統(tǒng)的可用性和正確性。
- 如果一個任務依賴于另一個任務的結果,或者需要等待另一個任務的完成,那么它就可能造成線程池中的一個線程被阻塞,導致其他任務無法及時得到執(zhí)行,甚至導致死鎖的問題。
解決方法
解決方法也很簡單,就是使用不同的線程池來執(zhí)行不同類型的任務,根據(jù)任務的特點和重要性來分配線程資源,避免一個任務影響另一個任務。具體來說,有以下幾個建議:
- 對于主要的業(yè)務邏輯,使用一個專門的線程池,根據(jù)業(yè)務的并發(fā)度和響應時間,設置合適的線程池參數(shù),保證業(yè)務的正常運行和高效處理。
- 對于次要的日志記錄、監(jiān)控等,使用一個單獨的線程池,根據(jù)任務的頻率和重要性,設置合適的線程池參數(shù),保證任務的異步執(zhí)行和不影響主業(yè)務。
- 對于有依賴關系的任務,使用一個單獨的線程池,根據(jù)任務的數(shù)量和復雜度,設置合適的線程池參數(shù),保證任務的有序執(zhí)行和不造成死鎖。
坑五:使用 ThreadLocal 和線程池的不兼容問題
ThreadLocal 是 Java 提供的一個工具類,它可以讓每個線程擁有自己的變量副本,從而實現(xiàn)線程間的數(shù)據(jù)隔離,比如存儲一些線程相關的上下文信息,如用戶 ID、請求 ID 等。這看起來很有用,但是如果和線程池一起使用,就可能會出現(xiàn)一些意想不到的問題,比如數(shù)據(jù)錯亂、內(nèi)存泄漏等。
問題原因
問題的原因是,ThreadLocal 和線程池的設計理念是相悖的,ThreadLocal 是基于線程的,而線程池是基于任務的。具體來說,有以下幾個問題:
- ThreadLocal 的變量是綁定在線程上的,而線程池的線程是可以復用的,如果一個線程執(zhí)行完一個任務后,沒有清理 ThreadLocal 的變量,那么這個變量就會被下一個執(zhí)行的任務繼承,導致數(shù)據(jù)錯亂的問題。
- ThreadLocal 的變量是存儲在 Thread 類的一個 ThreadLocalMap 類型的屬性中的,這個屬性是一個弱引用的 Map,它的鍵是 ThreadLocal 對象,而值是變量的副本。如果 ThreadLocal 對象被回收,那么它的鍵就會失效,但是值還會保留在 Map 中,導致內(nèi)存泄漏的問題。
解決方法
解決方法也很簡單,就是在使用 ThreadLocal 和線程池的時候,注意以下幾點:
- 在使用 ThreadLocal 的變量之前,要確保為每個線程設置了正確的初始值,避免使用上一個任務的遺留值。
- 在使用 ThreadLocal 的變量之后,要及時地清理 ThreadLocal 的變量,避免變量的副本被下一個執(zhí)行的任務繼承,或者占用內(nèi)存空間,導致內(nèi)存泄漏的問題。可以使用 try-finally 語句,或者使用 Java 8 提供的 AutoCloseable 接口,來實現(xiàn)自動清理的功能。
- 在使用 ThreadLocal 的時候,要注意線程池的大小和任務的數(shù)量,避免創(chuàng)建過多的 ThreadLocal 對象和變量的副本,導致內(nèi)存占用過大的問題??梢允褂靡恍┕ぞ?,如 VisualVM,來監(jiān)控線程池和 ThreadLocal 的狀態(tài),及時發(fā)現(xiàn)和解決問題。
總結
本文給大家介紹了線程池使用不當?shù)奈鍌€坑,分別是線程池中異常消失、線程池決絕策略設置錯誤、重復創(chuàng)建線程池導致內(nèi)存溢出、使用同一個線程池執(zhí)行不同類型的任務、使用 ThreadLocal 和線程池的不兼容問題,以及它們的問題原因和解決方法。
到此這篇關于五個Java中線程池使用不當?shù)谋芸又改系奈恼戮徒榻B到這了,更多相關Java線程池內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
-
Mybatis開發(fā)要點-resultType和resultMap有什么區(qū)別詳解
本文主要介紹了Mybatis開發(fā)要點-resultType和resultMap有什么區(qū)別詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧 2022-04-04
-
Java實現(xiàn)stream的三個常用方式(toMap,groupingBy,findFirst)
本文主要介紹了Java實現(xiàn)stream的三個常用方式,主要包括toMap,groupingBy,findFirst,具有一定的參考價值,感興趣的可以了解一下 2023-10-10
-
Java中的String對象數(shù)據(jù)類型全面解析
首先String不屬于8種基本數(shù)據(jù)類型,String是一個對象,因為對象的默認值是null,所以String的默認值也是null;但它又是一種特殊的對象,有其它對象沒有的一些特性 2012-11-11
-
hibernate-validator改進校驗框架validator?v0.4使用
這篇文章主要為大家介紹了改進?hibernate-validator,新一代校驗框架?validator?使用介紹?v0.4,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪<BR> 2023-03-03
-
詳解SpringBoot2.0的@Cacheable(Redis)緩存失效時間解決方案
這篇文章主要介紹了詳解SpringBoot2.0的@Cacheable(Redis)緩存失效時間解決方案,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧 2021-04-04
最新評論
線程池是 Java 多線程編程中的一個重要概念,它可以有效地管理和復用線程資源,提高系統(tǒng)的性能和穩(wěn)定性。但是線程池的使用也有一些注意事項和常見的錯誤,如果不小心,就可能會導致一些嚴重的問題,比如內(nèi)存泄漏、死鎖、性能下降等。
本文將介紹線程池使用不當?shù)奈鍌€坑,以及如何避免和解決它們,大綱如下,
坑一:線程池中異常消失
線程池執(zhí)行方法時要添加異常處理,這是一個老生常談的問題,可是直到最近我都有同事還在犯這個錯誤,所以我還是要講一下,不過我還提到了一種優(yōu)雅的線程池全局異常處理的方法,大家可以往下看。
問題原因
@Test public void test() throws Exception { ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( 5, 10, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100000)); Future<Integer> submit = threadPoolExecutor.execute(() -> { int i = 1 / 0; // 發(fā)生異常 return i; }); }
如上代碼,在線程池執(zhí)行任務時,沒有添加異常處理。導致任務內(nèi)部發(fā)生異常時,內(nèi)部錯誤無法被記錄下來。
解決方法
在線程池執(zhí)行任務方法內(nèi)添加 try/catch 處理,代碼如下,
@Test public void test() throws Exception { ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( 5, 10, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100000)); Future<Integer> submit = threadPoolExecutor.execute(() -> { try { int i = 1 / 0; return i; } catch (Exception e) { log.error(e.getMessage(), e); return null; } }); }
優(yōu)雅的進行線程池異常處理
當線程池調(diào)用任務方法很多時,那么每個線程池任務執(zhí)行的方法內(nèi)都要添加 try/catch 處理,這就不優(yōu)雅了,其實 ThreadPoolExecutor 線程池類支持傳入 ThreadFactory 參數(shù)用于自定義線程工廠,這樣我們在創(chuàng)建線程時,就可以指定 setUncaughtExceptionHandler 異常處理方法。
這樣就可以做到全局處理異常了,代碼如下,
ThreadFactory threadFactory = r -> { Thread thread = new Thread(r); thread.setUncaughtExceptionHandler((t, e) -> { // 記錄線程異常 log.error(e.getMessage(), e); }); return thread; }; ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( 5, 10, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100000)); threadPoolExecutor.execute(() -> { log.info("---------------------"); int i = 1 / 0; });
不過要注意的是上面 setUncaughtExceptionHandler 方法只能針對線程池的 execute 方法來全局處理異常。對于線程池的 submit 方法是無法處理的。
坑二:拒絕策略設置錯誤導致接口超時
在 Java 中,線程池拒絕策略可以說一個常見八股文問題。大家雖然都記住了線程池有四種決絕策略,可是實際代碼編寫中,我發(fā)現(xiàn)大多數(shù)人都只會用 CallerRunsPolicy 策略(由調(diào)用線程處理任務)。我吃過這個虧,因此也拿出來講講。
問題原因
曾經(jīng)有一個線上業(yè)務接口使用了線程池進行第三方接口調(diào)用,線程池配置里的拒絕策略采用的是 CallerRunsPolicy。示例代碼如下,
// 某個線上線程池配置如下 ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( 50, // 最小核心線程數(shù) 50, // 最大線程數(shù),當隊列滿時,能創(chuàng)建的最大線程數(shù) 60L, TimeUnit.SECONDS, // 空閑線程超過核心線程時,回收該線程的最大等待時間 new LinkedBlockingQueue<>(5000), // 阻塞隊列大小,當核心線程使用滿時,新的線程會放進隊列 new CustomizableThreadFactory("task"), // 自定義線程名 new ThreadPoolExecutor.CallerRunsPolicy() // 線程執(zhí)行的拒絕策略 ); threadPoolExecutor.execute(() -> { // 調(diào)用第三方接口 ... });
在第三方接口異常的情況下,線程池任務調(diào)用第三方接口一直超時,導致核心線程數(shù)、最大線程數(shù)堆積被占滿、阻塞隊列也被占滿的情況下,也就會執(zhí)行拒絕策略,但是由于使用的是 CallerRunsPolicy 策略,導致線程任務直接由我們的業(yè)務線程來執(zhí)行。
因為第三方接口異常,所以業(yè)務線程執(zhí)行也會繼繼續(xù)超時,線上服務采用的 Tomcat 容器,最終也就導致 Tomcat 的最大線程數(shù)也被占滿,進而無法繼續(xù)向外提供服務。
解決方法
首先我們要考慮業(yè)務接口的可用性,就算線程池任務被丟棄,也不應該影響業(yè)務接口。
在業(yè)務接口穩(wěn)定性得到保證的情況下,在考慮到線程池任務的重要性,不是很重要的話,可以使用 DiscardPolicy 策略直接丟棄,要是很重要,可以考慮使用消息隊列來替換線程池。
坑三:重復創(chuàng)建線程池導致內(nèi)存溢出
不知道大家有沒有犯過這個問題,不過我確實犯過,歸根結底還是寫代碼前,沒有思考好業(yè)務邏輯,直接動手,寫一步算一步 。所以說寫代碼的前的一些邏輯梳理、拆分、代碼設計很重要。
問題原因
這個問題的原因很簡單,就是在一個方法內(nèi)重復創(chuàng)建了線程池,在執(zhí)行完之后卻沒有關閉。比較經(jīng)典的就是在定時任務內(nèi)使用線程池時有可能犯這個問題,示例代碼如下,
@XxlJob("test") public void test() throws Exception { // 某個線上線程池配置如下 ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( 50, // 最小核心線程數(shù) 50, // 最大線程數(shù),當隊列滿時,能創(chuàng)建的最大線程數(shù) 60L, TimeUnit.SECONDS, // 空閑線程超過核心線程時,回收該線程的最大等待時間 new LinkedBlockingQueue<>(5000), // 阻塞隊列大小,當核心線程使用滿時,新的線程會放進隊列 new CustomizableThreadFactory("task"), // 自定義線程名 new ThreadPoolExecutor.CallerRunsPolicy() // 線程執(zhí)行的拒絕策略 ); threadPoolExecutor.execute(() -> { // 任務邏輯 ... }); }
當我們在定時任務中想使用線程池來縮短任務執(zhí)行時間時,千萬要注意別再任務內(nèi)創(chuàng)建了線程池,一旦犯了,基本都會在程序運行一段時間后發(fā)現(xiàn)程序突然間就掛了,留下了一堆內(nèi)存 dump 報錯的文件。
解決方法
使用線程池單例,切勿重復創(chuàng)建線程池。示例代碼如下,
// 某個線上線程池配置如下 ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( 50, // 最小核心線程數(shù) 50, // 最大線程數(shù),當隊列滿時,能創(chuàng)建的最大線程數(shù) 60L, TimeUnit.SECONDS, // 空閑線程超過核心線程時,回收該線程的最大等待時間 new LinkedBlockingQueue<>(5000), // 阻塞隊列大小,當核心線程使用滿時,新的線程會放進隊列 new CustomizableThreadFactory("task"), // 自定義線程名 new ThreadPoolExecutor.CallerRunsPolicy() // 線程執(zhí)行的拒絕策略 ); @XxlJob("test") public void test() throws Exception { threadPoolExecutor.execute(() -> { // 任務邏輯 // ... }); }
坑四:共用線程池執(zhí)行不同類型任務導致效率低下
有時候,我們可能會想要節(jié)省線程資源,把不同類型的任務都放到同一個線程池中執(zhí)行,比如主要的業(yè)務邏輯和次要的日志記錄、監(jiān)控等。這看起來很合理,但是實際上,這樣做可能會導致一個任務影響另一個任務,甚至導致死鎖的問題。
問題原因
問題的原因是,不同類型的任務可能有不同的執(zhí)行時間、優(yōu)先級、依賴關系等,如果放到同一個線程池中,就可能會出現(xiàn)以下幾種情況:
- 如果一個任務執(zhí)行時間過長,或者出現(xiàn)異常,那么它就會占用線程池中的一個線程,導致其他任務無法及時得到執(zhí)行,影響系統(tǒng)的吞吐量和響應時間。
- 如果一個任務的優(yōu)先級較低,或者不是很重要,那么它就可能搶占線程池中的一個線程,導致其他任務無法及時得到執(zhí)行,影響系統(tǒng)的可用性和正確性。
- 如果一個任務依賴于另一個任務的結果,或者需要等待另一個任務的完成,那么它就可能造成線程池中的一個線程被阻塞,導致其他任務無法及時得到執(zhí)行,甚至導致死鎖的問題。
解決方法
解決方法也很簡單,就是使用不同的線程池來執(zhí)行不同類型的任務,根據(jù)任務的特點和重要性來分配線程資源,避免一個任務影響另一個任務。具體來說,有以下幾個建議:
- 對于主要的業(yè)務邏輯,使用一個專門的線程池,根據(jù)業(yè)務的并發(fā)度和響應時間,設置合適的線程池參數(shù),保證業(yè)務的正常運行和高效處理。
- 對于次要的日志記錄、監(jiān)控等,使用一個單獨的線程池,根據(jù)任務的頻率和重要性,設置合適的線程池參數(shù),保證任務的異步執(zhí)行和不影響主業(yè)務。
- 對于有依賴關系的任務,使用一個單獨的線程池,根據(jù)任務的數(shù)量和復雜度,設置合適的線程池參數(shù),保證任務的有序執(zhí)行和不造成死鎖。
坑五:使用 ThreadLocal 和線程池的不兼容問題
ThreadLocal 是 Java 提供的一個工具類,它可以讓每個線程擁有自己的變量副本,從而實現(xiàn)線程間的數(shù)據(jù)隔離,比如存儲一些線程相關的上下文信息,如用戶 ID、請求 ID 等。這看起來很有用,但是如果和線程池一起使用,就可能會出現(xiàn)一些意想不到的問題,比如數(shù)據(jù)錯亂、內(nèi)存泄漏等。
問題原因
問題的原因是,ThreadLocal 和線程池的設計理念是相悖的,ThreadLocal 是基于線程的,而線程池是基于任務的。具體來說,有以下幾個問題:
- ThreadLocal 的變量是綁定在線程上的,而線程池的線程是可以復用的,如果一個線程執(zhí)行完一個任務后,沒有清理 ThreadLocal 的變量,那么這個變量就會被下一個執(zhí)行的任務繼承,導致數(shù)據(jù)錯亂的問題。
- ThreadLocal 的變量是存儲在 Thread 類的一個 ThreadLocalMap 類型的屬性中的,這個屬性是一個弱引用的 Map,它的鍵是 ThreadLocal 對象,而值是變量的副本。如果 ThreadLocal 對象被回收,那么它的鍵就會失效,但是值還會保留在 Map 中,導致內(nèi)存泄漏的問題。
解決方法
解決方法也很簡單,就是在使用 ThreadLocal 和線程池的時候,注意以下幾點:
- 在使用 ThreadLocal 的變量之前,要確保為每個線程設置了正確的初始值,避免使用上一個任務的遺留值。
- 在使用 ThreadLocal 的變量之后,要及時地清理 ThreadLocal 的變量,避免變量的副本被下一個執(zhí)行的任務繼承,或者占用內(nèi)存空間,導致內(nèi)存泄漏的問題。可以使用 try-finally 語句,或者使用 Java 8 提供的 AutoCloseable 接口,來實現(xiàn)自動清理的功能。
- 在使用 ThreadLocal 的時候,要注意線程池的大小和任務的數(shù)量,避免創(chuàng)建過多的 ThreadLocal 對象和變量的副本,導致內(nèi)存占用過大的問題??梢允褂靡恍┕ぞ?,如 VisualVM,來監(jiān)控線程池和 ThreadLocal 的狀態(tài),及時發(fā)現(xiàn)和解決問題。
總結
本文給大家介紹了線程池使用不當?shù)奈鍌€坑,分別是線程池中異常消失、線程池決絕策略設置錯誤、重復創(chuàng)建線程池導致內(nèi)存溢出、使用同一個線程池執(zhí)行不同類型的任務、使用 ThreadLocal 和線程池的不兼容問題,以及它們的問題原因和解決方法。
到此這篇關于五個Java中線程池使用不當?shù)谋芸又改系奈恼戮徒榻B到這了,更多相關Java線程池內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Mybatis開發(fā)要點-resultType和resultMap有什么區(qū)別詳解
本文主要介紹了Mybatis開發(fā)要點-resultType和resultMap有什么區(qū)別詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2022-04-04Java實現(xiàn)stream的三個常用方式(toMap,groupingBy,findFirst)
本文主要介紹了Java實現(xiàn)stream的三個常用方式,主要包括toMap,groupingBy,findFirst,具有一定的參考價值,感興趣的可以了解一下2023-10-10Java中的String對象數(shù)據(jù)類型全面解析
首先String不屬于8種基本數(shù)據(jù)類型,String是一個對象,因為對象的默認值是null,所以String的默認值也是null;但它又是一種特殊的對象,有其它對象沒有的一些特性2012-11-11hibernate-validator改進校驗框架validator?v0.4使用
這篇文章主要為大家介紹了改進?hibernate-validator,新一代校驗框架?validator?使用介紹?v0.4,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪<BR>2023-03-03詳解SpringBoot2.0的@Cacheable(Redis)緩存失效時間解決方案
這篇文章主要介紹了詳解SpringBoot2.0的@Cacheable(Redis)緩存失效時間解決方案,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2021-04-04