原來,這才是 JDK 推薦的線程關(guān)閉方式
點(diǎn)擊關(guān)注公眾號(hào),Java干貨及時(shí)送達(dá)
JDK 在線程的 Stop 方法時(shí)明確不得強(qiáng)行銷毀一個(gè)線程,要優(yōu)雅的退出線程。
-
任務(wù)執(zhí)行完成,或異常終止,任務(wù)認(rèn)為無需再占用線程。 -
線程池根據(jù)當(dāng)前任務(wù)執(zhí)行情況,伸縮線程池。當(dāng)任務(wù)執(zhí)行較少時(shí),退出空閑的線程。 -
服務(wù)或進(jìn)程在關(guān)閉階段,例如滾動(dòng)發(fā)布時(shí),需要退出線程、關(guān)閉線程池、關(guān)閉進(jìn)程。 -
定時(shí)任務(wù)、周期任務(wù)需要終止執(zhí)行時(shí),需要退出當(dāng)前線程。或者退出當(dāng)前任務(wù)的執(zhí)行。

while(config.isTaskEnable()) {//從配置中心獲取任務(wù)是否要終止//循環(huán)執(zhí)行業(yè)務(wù)邏輯。直到執(zhí)行完成退出,或者被終止。}
這種退出方式,是告知線程 “你應(yīng)該在合適時(shí)機(jī)退出”, 由線程自己選擇在合適的時(shí)機(jī)檢查該狀態(tài)。那么開發(fā)者在設(shè)計(jì)任務(wù)代碼時(shí),就要提前設(shè)計(jì) 合理的退出點(diǎn),在退出點(diǎn)檢查是否需要退出。
Thread.interrupt()
JDK 中提到了如果目標(biāo)線程沒有處于運(yùn)行態(tài),而是處于阻塞狀態(tài),自然無法檢查退出的狀態(tài)標(biāo)記,如何通知這個(gè)線程退出呢?
JDK: 如果目標(biāo)線程在一個(gè)條件變量上 wait,則其他線程應(yīng)該使用 interrupt 方法中斷目標(biāo)線程。
interrupt 的 JDK 注釋提到,
如果其他線程調(diào)用目標(biāo)線程的 interrupt 方法,
恰好目標(biāo)線程在調(diào)用。Object.wait(),object.join (),Object.sleep() 等方法時(shí),目標(biāo)線程的中斷位標(biāo)記被清除,同時(shí)目標(biāo)線程會(huì)立即從 sleep、wait 等調(diào)用中恢復(fù),并且被拋出 InterruptException。
如果目標(biāo)線程在 IO 操作中被阻塞,例如 io.channels.InterruptibleChannel,Channel 將被關(guān)閉,線程的中斷位被設(shè)置,同時(shí)目標(biāo)線程收到 java.nio.channels.ClosedByInterruptException。
如果目標(biāo)線程被阻塞在 java.nio.channels.Selector,線程中斷狀態(tài)被設(shè)置,然后目標(biāo)線程立即從 select 中返回非零值。
如果其他條件都不成立,該線程中斷位會(huì)被設(shè)置。
線程中斷位標(biāo)記了當(dāng)前線程是否處于被中斷狀態(tài),并且提供了 Thread.isInterrupted 方法查看當(dāng)前是否處于中斷位?那為什么目標(biāo)線程阻塞在 Object.wait(),Sleep() 方法時(shí),拋出了 interruptException,會(huì)取消標(biāo)記呢?實(shí)際上 interrupt 操作執(zhí)行兩件事,1)設(shè)置中斷位標(biāo)記 2)通過 unpark 喚醒目標(biāo)線程(park 和 unpark 分別可以阻塞線程和喚醒線程)。
然而目標(biāo)線程醒來時(shí)會(huì)檢查當(dāng)前是否處于中斷位,如果是 sleep 或者 wait 操作。如果處于中斷位則取消中斷位,拋出異常。取消中段位的原因應(yīng)該是一種規(guī)范,即拋出中斷異常,即通知了線程中斷,無需再用中段位標(biāo)記。
其他場景 2、場景 3 在被喚醒后,分別執(zhí)行對應(yīng)的中斷響應(yīng)策略。
interrupt 中斷邏輯是確定的,業(yè)務(wù)線程要考慮自己是否調(diào)用了 sleep、wait 或者 io、selector 等操作,根據(jù)不同的場景,選擇自己合適的中斷響應(yīng)策略。
那么推薦業(yè)務(wù)線程如何響應(yīng)中斷呢?
推薦的中斷響應(yīng)策略
立即響應(yīng)中斷
目標(biāo)線程的任務(wù)在 InterruptedException 異常處理中,要主動(dòng)回收資源,打印日志,退出任務(wù)執(zhí)行。
目標(biāo)線程如果沒有阻塞操作,例如 sleep、wait。可以通過 Thread.isInterrupted(),查看當(dāng)前中斷位狀態(tài),如果被中斷了,則采取以上第一步操作。
忽略中斷,交給上一層處理
所謂上一層,可以理解為是調(diào)用堆棧的上一層,例如本層代碼不負(fù)責(zé)處理中斷這個(gè)場景,那么 Interrupt 異常被拋出后,可以選擇如何方案:
拋出 InterruptedException 給上層,由上層代碼處理。
調(diào)用 Thread.interrupt()。重新設(shè)置中斷位標(biāo)記 (自己中斷自己)。由上游代碼在本層方法返回后,檢查中斷位標(biāo)記,進(jìn)行中斷處理。
當(dāng)然最推薦的方式還是拋出 InterruptedException,讓上游感知到下游調(diào)用鏈中存在阻塞,讓上游對中斷異常進(jìn)行處理。
千萬不要吞掉中斷
什么是吞掉中斷?例如當(dāng) sleep 拋出 InterruptedException 后,忽略異常,不執(zhí)行任何操作,繼續(xù)執(zhí)行業(yè)務(wù)邏輯。
for (int i = 0; i < cnt; i++) {try {//執(zhí)行業(yè)務(wù)邏輯Thread.sleep(10000);} catch (InterruptedException e) {System.out.println("被中斷");}System.out.println("子線程執(zhí)行中");}
while(true){callChildMethod();//調(diào)用下游方法,但是下游吞掉了中斷if (Thread.currentThread().isInterrupted()) {//回收資源,退出線程}}
-
不推薦強(qiáng)制銷毀線程,會(huì)導(dǎo)致資源無法被釋放,進(jìn)行中請求無法正常處理完,導(dǎo)致業(yè)務(wù)數(shù)據(jù)處于不可知的狀態(tài)。 -
Java 推薦優(yōu)雅退出線程。 -
業(yè)務(wù)層可以使用字段標(biāo)記,定期檢查是否需要退出任務(wù)。 -
Thread.interrupt 中斷目標(biāo)線程、isInterrupted 查詢中斷位標(biāo)記。 -
使用 Thread.interrupt 處理中斷也可以優(yōu)雅退出,但需要上下層堆棧都要關(guān)注中斷,不得吞掉中斷。
轉(zhuǎn)自:五陽神功,
鏈接:juejin.cn/post/7291564831710445622
往 期 推 薦
2、MySQL到底是 join 性能好,還是in一下更快呢?
3、Next.js支持在前端代碼中寫SQL,開倒車還是遙遙領(lǐng)先?
4、為什么國外JetBrains做 IDE 就可以養(yǎng)活自己,國內(nèi)不行?區(qū)別在哪?
![]()
點(diǎn)分享
![]()
點(diǎn)收藏
![]()
點(diǎn)點(diǎn)贊
![]()
點(diǎn)在看

