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