Java中如何正確的停掉線程
為什么不強制停止
對于 Java 而言,最正確的停止線程的方式是使用 interrupt。但 interrupt僅僅起到通知被停止線程的作用。而對于被停止的線程而言,它擁有完全的自主權,它既可以選擇立即停止,也可以選擇一段時間后停止,也可以選擇壓根不停止。
為什么 Java 不提供強制停止線程的能力呢?
事實上,Java 希望程序間能夠相互通知、相互協(xié)作地管理線程,因為如果不了解對方正在做的工作,貿然強制停止線程就可能會造成一些安全的問題。
比如:
線程正在寫入一個文件,這時收到終止信號,它就需要根據自身業(yè)務判斷,是選擇立即停止,還是將整個文件寫入成功后停止。如果選擇立即停止就可能造成數(shù)據不完整,不管是中斷命令發(fā)起者,還是接收者都不希望數(shù)據出現(xiàn)問題。
如何用interrupt停止線程
while (!Thread.currentThread().isInterrupted()
&& more work to do) {
do more work
}一旦調用某個線程的 interrupt() 之后,這個線程的中斷標記位就會被設置成 true。每個線程都有這樣的標記位,當線程執(zhí)行時,應該定期檢查這個標記位,如果標記位被設置成 true,就說明有程序想終止該線程。
回到源碼,可以看到在 while 循環(huán)體判斷語句中,首先通過 Thread.currentThread().isInterrupt() 判斷線程是否被中斷,隨后檢查是否還有工作要做。&& 邏輯表示只有當兩個判斷條件同時滿足的情況下,才會去執(zhí)行下面的工作。
來段代碼瞅瞅。
public class StopThread implements Runnable {
@Override
public void run() {
int count = 0;
while (!Thread.currentThread().isInterrupted() && count < 1000) {
System.out.println("count = " + count++);
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new StopThread());
thread.start();
Thread.sleep(5);
thread.interrupt();
}
}在 StopThread 類的 run() 方法中,首先判斷線程是否被中斷,然后判斷 count 值是否小于 1000。
這個線程的工作內容很簡單,就是打印 0~999 的數(shù)字,每打印一個數(shù)字 count 值加 1,可以看到,線程會在每次循環(huán)開始之前,檢查是否被中斷了。接下來在 main 函數(shù)中會啟動該線程,然后休眠 5 毫秒后立刻中斷線程,該線程會檢測到中斷信號,于是在還沒打印完1000個數(shù)的時候就會停下來,這種就屬于通過 interrupt 正確停止線程的情況。
sleep 期間能否感受到中斷
先說結論,可以。
public class StopDuringSleep {
public static void main(String[] args) throws InterruptedException {
Runnable runnable = () -> {
int num = 0;
try {
while (!Thread.currentThread().isInterrupted() && num <= 1000) {
System.out.println(num);
num++;
Thread.sleep(1000000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
};
Thread thread = new Thread(runnable);
thread.start();
Thread.sleep(5);
thread.interrupt();
}
}運行后的結果猜這么著,程序會拋出異常

如果 sleep、wait 等可以讓線程進入阻塞的方法使線程休眠了,而處于休眠中的線程被中斷,那么線程是可以感受到中斷信號的,并且會拋出一個 InterruptedException 異常,同時清除中斷信號,將中斷標記位設置成 false。這樣一來就不用擔心長時間休眠中線程感受不到中斷了,因為即便線程還在休眠,仍然能夠響應中斷通知,并拋出異常。
但是這樣只能相應一次中斷信號了,怎么辦?
合理利用好 try/catch
在實際開發(fā)中不能盲目吞掉中斷,如果不在方法簽名中聲明,也不在 catch 語句塊中再次恢復中斷,而是在 catch 中不作處理,稱這種行為是“屏蔽了中斷請求”。如果盲目地屏蔽了中斷請求,會導致中斷信號被完全忽略,最終導致線程無法正確停止。
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// 此處處理中斷異常請求
}停止線程的方式有幾種
void shutdown;boolean isShutdown;boolean isTerminated;boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;List<Runnable> shutdownNow;
下面就對這些方法逐一展開。
shutdown()
調用 shutdown() 方法之后線程池并不是立刻就被關閉,因為這時線程池中可能還有很多任務正在被執(zhí)行,或是任務隊列中有大量正在等待被執(zhí)行的任務,調用 shutdown() 方法后線程池會在執(zhí)行完正在執(zhí)行的任務和隊列中等待的任務后才徹底關閉。
但這并不代表 shutdown() 操作是沒有任何效果的,調用 shutdown() 方法后如果還有新的任務被提交,線程池則會根據拒絕策略直接拒絕后續(xù)新提交的任務。
isShutdown()
它可以返回 true 或者 false 來判斷線程池是否已經開始了關閉工作,也就是是否執(zhí)行了 shutdown 或者 shutdownNow 方法。這里需要注意,如果調用 isShutdown() 方法的返回的結果為 true 并不代表線程池此時已經徹底關閉了,這僅僅代表線程池開始了關閉的流程,也就是說,此時可能線程池中依然有線程在執(zhí)行任務,隊列里也可能有等待被執(zhí)行的任務。
isTerminated()
這個方法可以檢測線程池是否真正“終結”了,這不僅代表線程池已關閉,同時代表線程池中的所有任務都已經都執(zhí)行完畢了,因為剛才說過,調用 shutdown 方法之后,線程池會繼續(xù)執(zhí)行里面未完成的任務,不僅包括線程正在執(zhí)行的任務,還包括正在任務隊列中等待的任務。
比如此時已經調用了 shutdown 方法,但是有一個線程依然在執(zhí)行任務,那么此時調用 isShutdown 方法返回的是 true ,而調用 isTerminated 方法返回的便是 false ,因為線程池中還有任務正在在被執(zhí)行,線程池并沒有真正“終結”。直到所有任務都執(zhí)行完畢了,調用 isTerminated() 方法才會返回 true,這表示線程池已關閉并且線程池內部是空的,所有剩余的任務都執(zhí)行完畢了。
awaitTermination()
第四個方法叫作 awaitTermination(),它本身并不是用來關閉線程池的,而是主要用來判斷線程池狀態(tài)的。比如給 awaitTermination 方法傳入的參數(shù)是 10 秒,那么它就會陷入 10 秒鐘的等待,直到發(fā)生以下三種情況之一:
- 等待期間(包括進入等待狀態(tài)之前)線程池已關閉并目所有已提交的任務(包括正在執(zhí)行的和隊列中等待的都執(zhí)行完畢,相當于線程池已經“終結”了,方法便會返回true
- 等待超時時間到后,第一種線程池“終結”的情況始終未發(fā)生,方法返回 false
- 等待期間線程被中斷,方法會拋出
Interruptedexception異常
等待期間(包括進入等待狀態(tài)之前)線程池已關閉并且所有已提交的任務(包括正在執(zhí)行的和隊列中等待的)都執(zhí)行完畢,相當于線程池已經“終結”了,方法便會返回 true;
等待超時時間到后,第一種線程池“終結”的情況始終未發(fā)生,方法返回 false;等待期間線程被中斷,方法會拋出 InterruptedException 異常。
shutdownNow()
最后一個方法是 shutdownNow(),也是 5 種方法里功能最強大的,它與第一種 shutdown 方法不同之處在于名字中多了一個單詞 Now,也就是表示立刻關閉的意思。
在執(zhí)行 shutdownNow 方法之后,首先會給所有線程池中的線程發(fā)送 interrupt 中斷信號,嘗試中斷這些任務的執(zhí)行,然后會將任務隊列中正在等待的所有任務轉移到一個 List 中并返回,可以根據返回的任務 List 來進行一些補救的操作,例如記錄在案并在后期重試。
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(STOP);
interruptWorkers();
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}源碼中有一行 interruptWorkers() 代碼,這行代碼會讓每一個已經啟動的線程都中斷,這樣線程就可以在執(zhí)行任務期間檢測到中斷信號并進行相應的處理,提前結束任務。
這里需要注意的是,由于 Java 中不推薦強行停止線程的機制的限制,即便調用了 shutdownNow 方法,如果被中斷的線程對于中斷信號不理不睬,那么依然有可能導致任務不會停止。
總結
中斷和關閉線程的方式五花八門,看起來很相似,其實里頭大有門道。處理不好,可是會導致程序崩潰的。
到此這篇關于Java中如何正確的停掉線程的文章就介紹到這了,更多相關java停掉線程內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
springboot 自定義權限標簽(tld),在freemarker引用操作
這篇文章主要介紹了springboot 自定義權限標簽(tld),在freemarker引用操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-09-09
Java中List使用stream流轉成map的幾種方式詳解
Stream是Java8中處理集合的關鍵抽象概念,它可以指定你希望對集合進行的操作,可以執(zhí)行非常復雜的查找、過濾和映射數(shù)據等操作,下面這篇文章主要給大家介紹了關于Java中List使用stream流轉成map的幾種方式,需要的朋友可以參考下2023-04-04
淺析javax.servlet.Servlet,ServletContext接口
本篇文章是對javax.servlet.Servlet,ServletContext接口進行了纖細的分析介紹,需要的朋友參考下2013-07-07
Java使用jni清屏功能的實現(xiàn)(只針對cmd)
JNI是Java Native Interface的縮寫,它提供了若干的API實現(xiàn)了Java和其他語言的通信(主要是C&C++)。這篇文章主要介紹了Java使用jni清屏功能的實現(xiàn)(只針對cmd) ,感興趣的朋友跟隨腳本之家小編一起學習吧2018-05-05
springboot使用kafka推送數(shù)據到服務端的操作方法帶認證
在使用Kafka進行數(shù)據推送時,遇到認證問題導致連接失敗,本文詳細介紹了Kafka的認證配置過程,包括配置文件的引入和參數(shù)設置,實際測試表明,需要正確配置sasl.jaas.config以及其他認證參數(shù),探討了配置文件是否可以同時存在多個配置塊的可能性,并提出了實際操作中的注意事項2024-11-11

