線程池是 Java 多線程編程中的一個重要概念,它可以有效地管理和復用線程資源,提高系統的性能和穩定性。但是線程池的使用也有一些注意事項和常見的錯誤,如果不小心,就可能會導致一些嚴重的問題,比如內存泄漏、死鎖、性能下降等。最后文末還有免費紅包封面可以領取,回饋給各位讀者朋友。
本文將介紹線程池使用不當的五個坑,以及如何避免和解決它們,大綱如下,
線程池執行方法時要添加異常處理,這是一個老生常談的問題,可是直到最近我都有同事還在犯這個錯誤,所以我還是要講一下,不過我還提到了一種優雅的線程池全局異常處理的方法,大家可以往下看。
@Testpublic 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; // 發生異常 return i; });}
如上代碼,在線程池執行任務時,沒有添加異常處理。導致任務內部發生異常時,內部錯誤無法被記錄下來。
在線程池執行任務方法內添加 try/catch 處理,代碼如下,
@Testpublic 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; } });}
當線程池調用任務方法很多時,那么每個線程池任務執行的方法內都要添加 try/catch 處理,這就不優雅了,其實 ThreadPoolExecutor 線程池類支持傳入 ThreadFactory 參數用于自定義線程工廠,這樣我們在創建線程時,就可以指定 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 中,線程池拒絕策略可以說一個常見八股文問題。大家雖然都記住了線程池有四種決絕策略,可是實際代碼編寫中,我發現大多數人都只會用 CallerRunsPolicy 策略(由調用線程處理任務)。我吃過這個虧,因此也拿出來講講。
曾經有一個線上業務接口使用了線程池進行第三方接口調用,線程池配置里的拒絕策略采用的是 CallerRunsPolicy。示例代碼如下,
// 某個線上線程池配置如下ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( 50, // 最小核心線程數 50, // 最大線程數,當隊列滿時,能創建的最大線程數 60L, TimeUnit.SECONDS, // 空閑線程超過核心線程時,回收該線程的最大等待時間 new LinkedBlockingQueue<>(5000), // 阻塞隊列大小,當核心線程使用滿時,新的線程會放進隊列 new CustomizableThreadFactory("task"), // 自定義線程名 new ThreadPoolExecutor.CallerRunsPolicy() // 線程執行的拒絕策略);threadPoolExecutor.execute(() -> { // 調用第三方接口 ...});
在第三方接口異常的情況下,線程池任務調用第三方接口一直超時,導致核心線程數、最大線程數堆積被占滿、阻塞隊列也被占滿的情況下,也就會執行拒絕策略,但是由于使用的是 CallerRunsPolicy 策略,導致線程任務直接由我們的業務線程來執行。
因為第三方接口異常,所以業務線程執行也會繼繼續超時,線上服務采用的 Tomcat 容器,最終也就導致 Tomcat 的最大線程數也被占滿,進而無法繼續向外提供服務。
首先我們要考慮業務接口的可用性,就算線程池任務被丟棄,也不應該影響業務接口。
在業務接口穩定性得到保證的情況下,在考慮到線程池任務的重要性,不是很重要的話,可以使用 DiscardPolicy 策略直接丟棄,要是很重要,可以考慮使用消息隊列來替換線程池。
不知道大家有沒有犯過這個問題,不過我確實犯過,歸根結底還是寫代碼前,沒有思考好業務邏輯,直接動手,寫一步算一步
本文鏈接:http://www.www897cc.com/showinfo-26-72429-0.html【踩坑指南】線程池使用不當的五個坑
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com
上一篇: 深入了解Java 8 新特性-日期時間API之LocalDateTime類
下一篇: 什么是數據同步利器DataX,如何使用?