JDK 在線程的 Stop 方法時明確不得強行銷毀一個線程,要優(yōu)雅的退出線程。
何謂優(yōu)雅退出線程,即業(yè)務將進行中請求正確被處理,取消待執(zhí)行請求,執(zhí)行資源回收,最終Thread Runable run方法 return 結(jié)束執(zhí)行。
首先問為什么要退出一個線程,再提問如何退出一個線程
1需要線程退出的常見場景
任務執(zhí)行完成,或異常終止,任務認為無需再占用線程。
線程池根據(jù)當前任務執(zhí)行情況,伸縮線程池。當任務執(zhí)行較少時,退出空閑的線程。
服務或進程在關閉階段,例如滾動發(fā)布時,需要退出線程、關閉線程池、關閉進程。
定時任務、周期任務需要終止執(zhí)行時,需要退出當前線程?;蛘咄顺霎斍叭蝿盏膱?zhí)行。
總之既然能創(chuàng)建一個線程,就會有退出一個線程的能力。也會有退出線程的場景。
關閉一個線程的方式分為兩種類型:通知線程主動關閉和強行關閉銷毀線程。
2優(yōu)雅關閉 or 強行關閉
實際上強行關閉一個線程,壞處很多,假如要釋放分布式鎖前,突然關閉線程,那么這個分布式鎖就無法釋放。導致后續(xù)正常請求加鎖失敗被阻塞,影響用戶提單等。強行關閉一個線程無異于給服務器直接斷電。
3其他語言和 Java 語言退出線程的方式
除了 Java 其他語言如何退出線程呢,實際上每一種實現(xiàn)方式都有。例如 C++ 中可以通過 ExitThread、TerminateThread 強行終止線程執(zhí)行。linux 既提供了 pthread_exit C 語言系統(tǒng)調(diào)用強行關閉線程,也提供了pthread_cancel通知線程關閉等優(yōu)雅退出方式。
Java 也分別提供優(yōu)雅和強制兩種退出方式,但是目前 JDK 中明確極不推薦強制中斷線程,在Thread.stop()強制中斷線程的注釋中, JDK 這樣解釋
Thread.stop()這種方法本身就是不安全的,Stop 一個線程會隨之解鎖這個線程所持有的監(jiān)視器(可以理解為鎖),如果受這些監(jiān)視器(鎖)保護的臨界對象處在不一致狀態(tài),則其他線程可能會看到這些對象處于不一致狀態(tài),那么將導致未知的行為。對Thread.Stop()的調(diào)用應該被簡單的代碼代替,例如 修改一個變量,目標線程定期檢查這個變量,有序從 run 方法 return 出來。如果目標線程在一個條件變量上 wait,則其他線程應該使用 interrupt 方法中斷目標線程。
實際上關閉一個線程強行和通知是兩種理念,即是否應該相信線程任務的開發(fā)者優(yōu)雅的、快速的主動退出線程,而不是被其他線程強制終止。在 Java 中,退出線程的方式只有一種推薦,即優(yōu)雅退出,并且 JDK 也給了建議,通過修改變量,由目標線程定期檢查狀態(tài)。或者通過 interrupt 中斷方式通知目標線程。
下面我們探討下如何優(yōu)雅退出一個線程?
4優(yōu)雅退出線程
有哪些方式呢?
業(yè)務字段標記
業(yè)務系統(tǒng)經(jīng)常遇到終止一個任務的訴求,例如系統(tǒng)中存在定時任務,例如外賣券包在過期后,未使用的金額,自動給用戶退款。假設任務執(zhí)行中,我需要重新制定任務的入?yún)?,需要先終止任務。如何做呢?
大部分任務類代碼都會循環(huán)處理,例如掃描全表執(zhí)行某個業(yè)務邏輯。一定存在循環(huán)處理的場景,可以在循環(huán)入口處判斷任務是否需要終止執(zhí)行,這樣通過控制這個字段,我們就可以終止任務執(zhí)行。
具體實施時,可以通過配置中心控制某一個任務是否要終止。
while(config.isTaskEnable()){ //從配置中心獲取任務是否要終止 //循環(huán)執(zhí)行業(yè)務邏輯。直到執(zhí)行完成退出,或者被終止。 }
這種退出方式,是告知線程 “你應該在合適時機退出”, 由線程自己選擇在合適的時機檢查該狀態(tài)。那么開發(fā)者在設計任務代碼時,就要提前設計 合理的退出點,在退出點檢查是否需要退出。
Thread.interrupt()
JDK 中提到了如果目標線程沒有處于運行態(tài),而是處于阻塞狀態(tài),自然無法檢查退出的狀態(tài)標記,如何通知這個線程退出呢?
JDK: 如果目標線程在一個條件變量上 wait,則其他線程應該使用 interrupt 方法中斷目標線程。
interrupt 的 JDK 注釋提到,
如果其他線程調(diào)用目標線程的 interrupt 方法,
恰好目標線程在調(diào)用。Object.wait(),object.join (),Object.sleep()等方法時,目標線程的中斷位標記被清除,同時目標線程會立即從 sleep、wait 等調(diào)用中恢復,并且被拋出InterruptException。
如果目標線程在 IO 操作中被阻塞,例如io.channels.InterruptibleChannel,Channel 將被關閉,線程的中斷位被設置,同時目標線程收到java.nio.channels.ClosedByInterruptException。
如果目標線程被阻塞在java.nio.channels.Selector,線程中斷狀態(tài)被設置,然后目標線程立即從 select 中返回非零值。
如果其他條件都不成立,該線程中斷位會被設置。
線程中斷位標記了當前線程是否處于被中斷狀態(tài),并且提供了Thread.isInterrupted方法查看當前是否處于中斷位?那為什么目標線程阻塞在Object.wait(),Sleep()方法時,拋出了interruptException,會取消標記呢?實際上 interrupt 操作執(zhí)行兩件事,1)設置中斷位標記 2)通過 unpark 喚醒目標線程(park 和 unpark 分別可以阻塞線程和喚醒線程)。
然而目標線程醒來時會檢查當前是否處于中斷位,如果是 sleep 或者 wait 操作。如果處于中斷位則取消中斷位,拋出異常。取消中段位的原因應該是一種規(guī)范,即拋出中斷異常,即通知了線程中斷,無需再用中段位標記。
其他場景 2、場景 3 在被喚醒后,分別執(zhí)行對應的中斷響應策略。
interrupt 中斷邏輯是確定的,業(yè)務線程要考慮自己是否調(diào)用了 sleep、wait 或者 io、selector 等操作,根據(jù)不同的場景,選擇自己合適的中斷響應策略。
那么推薦業(yè)務線程如何響應中斷呢?
推薦的中斷響應策略
立即響應中斷
目標線程的任務在InterruptedException異常處理中,要主動回收資源,打印日志,退出任務執(zhí)行。
目標線程如果沒有阻塞操作,例如 sleep、wait??梢酝ㄟ^Thread.isInterrupted(),查看當前中斷位狀態(tài),如果被中斷了,則采取以上第一步操作。
忽略中斷,交給上一層處理
所謂上一層,可以理解為是調(diào)用堆棧的上一層,例如本層代碼不負責處理中斷這個場景,那么 Interrupt 異常被拋出后,可以選擇如何方案:
拋出InterruptedException給上層,由上層代碼處理。
調(diào)用Thread.interrupt()。重新設置中斷位標記 (自己中斷自己)。由上游代碼在本層方法返回后,檢查中斷位標記,進行中斷處理。
當然最推薦的方式還是拋出InterruptedException,讓上游感知到下游調(diào)用鏈中存在阻塞,讓上游對中斷異常進行處理。
千萬不要吞掉中斷
什么是吞掉中斷?例如當 sleep 拋出InterruptedException后,忽略異常,不執(zhí)行任何操作,繼續(xù)執(zhí)行業(yè)務邏輯。
for(inti=0;i如果這樣處理,中斷異常被忽略,中斷標記位也被忽略。即便上游方法對中斷有處理策略,也無法感知到中斷。例如上游調(diào)用可能會判斷。
while(true){
callChildMethod();//調(diào)用下游方法,但是下游吞掉了中斷 if(Thread.currentThread().isInterrupted()){ //回收資源,退出線程 } }有人會問,既然上層都能知道處理中斷,為什么下層方法開發(fā)者會不記得拋出中斷或重置中斷位呢?
因為上下兩層,很可能不是一個開發(fā)者。例如上層是通用的框架代碼,定義了任務的指定邏輯,提供了擴展點方法,下游只需要實現(xiàn)擴展方法即可。但是另一個開發(fā)者在實現(xiàn)擴展點方法時,吞掉了中斷異常,導致本來框架層已經(jīng)處理好中斷了,但還是無法響應中斷。
所以中斷的響應是需要上下層,每一層代碼邏輯都需要考慮的事情。就算框架層處理好中斷異常處理,業(yè)務邏輯層也要關注中斷處理。
最后提醒一下,Thread.interrupted方法會返回當前中斷標記,并且取消中斷位。如果只查詢中斷位,不想清理,可以使用Thread.isInterrupted()。
5總結(jié)
不推薦強制銷毀線程,會導致資源無法被釋放,進行中請求無法正常處理完,導致業(yè)務數(shù)據(jù)處于不可知的狀態(tài)。
Java 推薦優(yōu)雅退出線程。
業(yè)務層可以使用字段標記,定期檢查是否需要退出任務。
Thread.interrupt中斷目標線程、isInterrupted查詢中斷位標記。
使用Thread.interrupt處理中斷也可以優(yōu)雅退出,但需要上下層堆棧都要關注中斷,不得吞掉中斷。
編輯:黃飛
-
JAVA
+關注
關注
19文章
2952瀏覽量
104479 -
C語言
+關注
關注
180文章
7594瀏覽量
135856 -
C++
+關注
關注
21文章
2100瀏覽量
73453 -
JDK
+關注
關注
0文章
81瀏覽量
16567 -
線程
+關注
關注
0文章
503瀏覽量
19634
原文標題:JDK 推薦的線程關閉方式
文章出處:【微信號:magedu-Linux,微信公眾號:馬哥Linux運維】歡迎添加關注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關推薦
評論