如何優雅關閉乙個執行緒

2021-10-22 05:38:08 字數 3202 閱讀 6329

當我們去thread類裡面找相關的介面時,發現有 個stop方法,看上去非常適合用來終止乙個執行緒,但是這個方法上面標了個@deprecated註解,非常明顯,這是乙個廢棄方法,不建議使用它。主要有兩個方面的原因:

因為這個方法會將執行緒直接殺掉,沒有任何喘息機會,一旦執行緒被殺死,後面的**邏輯就再也無法得到執行,而且我們無法確定執行緒關閉的時機,也就是說執行緒有可能在任何一行**突然停止執行,這是非常危險的。

假如這個執行緒正持有某個鎖,貿然將其殺死,會導致該執行緒持有的鎖馬上被釋放,而曾經被該鎖保護的資源,可能正處於一種非原子的狀態中,此時被其他執行緒訪問到,會產生不可預知的風險。

針對於第二種情況,可能不是很好理解,下面通過乙個例子,說明一下:

public class stopdemo 

private static int num;

private static class changethread extends thread

@override

public void run() }}

}private static class readthread extends thread

@override

public void run() ",num);

break;}}

}}}}

首先,在main方法中,我們啟動了兩個執行緒,其中乙個是change-thread執行緒,用於修改變數num的值,先加1等待1s後再減1,另乙個是read-thread執行緒用於讀取共享變數num的值,如果不為0則列印日誌並退出。

由於讀寫執行緒用了同一把互斥鎖,所以對於共享變數num的讀和寫是互斥的,正常情況下寫執行緒一定是完成+1再休眠1s再-1這樣的原子操作後,才會讓釋放鎖,因此讀執行緒讀到的值一定是0,不會列印日誌也不會退出。但是,由於我們在第8行**執行了changethread.stop(),可能導致寫執行緒將變數加1後就直接退出了,最終讀執行緒讀到的值是1而不是0,也退出迴圈。

既然stop()不建議使用,那是否有其他辦法用來優雅的停止乙個執行緒呢?答案是必須的,那就是兩階段終止(two-phase termination)方案。兩階段終止的兩個階段分別是指:

準備階段:發出終止指令,通過設定中斷標誌,並傳送中斷訊號,「通知」目標執行緒,可以準備停止了。

執行階段:響應終止指令,接收到中斷訊號及標誌,在此基礎決定執行緒退出時機,並執行適當清理工作。

設定中斷標誌:中斷標誌的作用是標識執行緒已經中斷了,當執行緒讀到這個標識後,就可以執行退出操作了。

發出中斷訊號:光設定中斷標誌還不行,如果執行緒當前處於阻塞狀態,就算設定了中斷標誌,執行緒也無法檢測到,這時就需要我們呼叫目標執行緒的中斷方法interrupt(),將其從阻塞狀態中喚醒,而目標執行緒通過捕獲interruptedexception異常,來偵測這個中斷訊號。需要注意的是,執行緒類中跟中斷相關的api主要有以下三個,特別容易混淆,需要注意區分:

方法簽名

作用public void interrupt()

中斷執行緒

public static boolean interrupted()

判斷當前執行緒是否為中斷狀態並清除中斷狀態

public boolean isinterrupted()

判斷執行緒是否為中斷狀態

可以看出,兩階段終止方案相比於stop()方案要更加優雅,打個比方,如果說stop像是不問青紅皂白直接將罪犯就地正法,那兩階段終止方案就像是先將罪犯收押,待事情水落石出之後,再執行相應處罰,相對而言,這種方式更加人性化也很好的避免了冤假錯案的發生。

public class twophaseterminationdemo 

private static class downloadthread extends thread

@override

public void run()

try

} catch (interruptedexception e) }}

public void stopme()

public boolean isterminated()}}

原因是執行緒中斷異常**獲後,它的中斷狀態已經被jvm給清除了,所以我們需要重新設定一下執行緒的中斷狀態,在第34行加上以下**

thread.currentthread().interrupt();
但是不要高興的太早,因為這種寫法並不完美,它依賴於執行緒的中斷狀態來退出執行緒,如果目標執行緒的**中呼叫了第三方類庫的介面,而這些介面在捕獲中斷異常後,清空了執行緒中斷狀態,但是沒有重置,就會導致上面描述的那種錯誤情況,所以我們需要尋求更可靠的解決方案。

上面有提到,我們不能依賴於執行緒自身的中斷狀態,那麼正確的做法應該怎麼處理呢?其實只要自定義乙個中斷標誌就行了。具體做法如下:

public class improvetwophaseterminationdemo 

private static class downloadthread extends thread

@override

public void run()

try

} catch (interruptedexception e) }}

public void stopme()

public boolean isterminated()}}

可以看到,第15行加上了乙個終止標誌terminated,呼叫stopme()方法的時候,將terminated設定為true,通過這個標誌,我們就可以不依賴於執行緒自身的中斷狀態,而將執行緒進行中斷了。

這篇文章主要講解了如何優雅的關閉乙個執行緒,首先我們應該避免使用stop()方法,這種方法簡單粗暴但具有不確定性,容易造成bug,正確的做法是通過兩階段終止方案,先發出中斷請求,設定執行緒為中斷狀態,當執行緒偵測到中斷狀態後,再去執行中斷後的清理邏輯。

如何優雅地關閉乙個socket

如何優雅地關閉乙個socket 1.關閉socket時究竟做了什麼 關閉socket分為主動關閉 active closure 和被動關閉 passive closure 兩種情況。前者是指有本地主機主動發起的關閉 而後者則是指本地主機檢測到遠端主機發起關閉之後,作出回應,從而關閉整個連線。其狀態圖...

如何優雅的停止乙個執行緒

首先我們先了解下,執行緒是分為使用者執行緒和守護執行緒的,他們的區別就在 使用者執行緒當我們的主線程停止後,使用者執行緒不會隨著主線程停止。守護執行緒當我們的主線程停止後,會跟隨著主線程一起停止。一般我們建立的都是使用者執行緒,那守護執行緒有哪些呢,例如跟程式一起啟動的gc執行緒就是守護執行緒。停止...

C 如何優雅的結束乙個執行緒

有乙個無線迴圈的執行緒需要時時接收資料,如下 class eventclass event recedatasdone?invoke this,eventargs.empty public void stoprecedatas class program ec.startrecedatas syst...