併發程式設計 —— 談談執行緒中斷

莫那·魯道發表於2018-05-19

前言

如何中斷一個執行緒,肯定不會使用 stop。而是使用 interrupt 方法。同時,我們知道,中斷一個執行緒只是打個標誌位。不會真的中斷執行緒,但,如果執行緒是阻塞狀態的呢?

而 Java 中,想要阻塞一個執行緒有很多種方式。

  1. synchronized
  2. Object.wait()
  3. Lock
  4. Condition.await()
  5. LockSupport.park()
  6. Thread.join()

當然上面都是很簡單的說說,API 並不是那麼的詳細。像 Lock 的 api 就有響應中斷的。 還有 Thread.sleep()。這個大家都知道,肯定是響應中斷的。

下面就來解決我們的問題。

解開迷霧

仔細分析一下,中斷其實有 2 種狀態,執行時中斷,阻塞時中斷。

顧名思義,執行時中斷指的是執行緒執行時,我們中斷他,當然,這個對執行緒毫無影響,只能通過標誌位來判斷。

而阻塞時中斷就分為 2 種。一種是在等待鎖的時候中斷,一種是進入鎖的時候,wait 的時候中斷。

例如一個 synchronized 同步塊,當多執行緒訪問同步塊時,同步塊外的就是等待鎖的狀態。進入鎖了,執行 wait 方法,也是阻塞的狀態。

雖然都是阻塞的狀態,但這兩種阻塞狀態是不同的。

基於前言中的阻塞方式,我們一個個來分析。

併發程式設計 —— 談談執行緒中斷

總結一下

非 Lock 介面,即 synchronized 和 Object 還有 Thread 的相關方法,除了 synchronized 不會響應中斷,其他的都會響應中斷並丟擲異常。

Lock 介面,無論是使用 lock 系列方法,還是 Condition 的 await 系列方法,都可隨心所欲,想使用什麼模式,就使用什麼模式。在日常的開發,這個特性還是非常有用的。

相比而言,Lock 相關的介面更加的靈活,對於執行緒中斷的響應和處理可自行設定,而非 Lock 介面則需要了解他們的中斷特性.

例如 sleep 方法和 wait 方法則會清除中斷狀態。

Lock 系列方法丟擲異常後,也是會清除中斷狀態的。

Lock 清除中斷狀態的手段則是 Thread.interrupted 方法。

為什麼要清除中斷狀態呢?如果下次有執行緒再次中斷,此時便可以判斷。

有點需要注意,Thread.interrupted 靜態方法和 Thraed#isInterrupted() 成員方法的區別

他們都是呼叫的 Thread 類的 private native boolean isInterrupted(boolean ClearInterrupted) 方法。

這個方法有個布林引數,true 是清除中斷狀態,false 則不清除。使用的時候需要注意。

拾遺

執行緒池的 shutDown 和 shutDownNow 方法是通過設定執行緒的中斷來停止執行緒的。

shutDown 必須等待任務執行完畢,才會執行執行緒池執行緒的 interrupt 方法。 shutDownNow 則不會,直接執行 interrupt 方法。

所以,當你執行一個 shutDown 方法的時候,必須等待任務執行完畢才能設定中斷狀態。你程式碼中設定的那些響應中斷方法是起不到作用的。真正起作用的,是執行緒池內部設定的狀態變數。設定中斷的目的則是打斷阻塞在佇列上的執行緒。

當你執行 shutDownNow 方法的時候,執行緒池會執行所有活動執行緒的 interrupt 方法,如果你的任務中恰好有以上的那些 響應中斷 的方法。那麼,就可以立即中斷執行緒。如果沒有,老實等待任務執行結束。

有趣的事情

執行緒池的 Worker 初始化的時候,會將 AQS 的 state 變數設定為 -1 ,防止使用者執行 shutDown 方法試圖停止執行緒。當將要執行真正的任務的時候,會將這個 AQS 變數設定為 0,這個時候,使用者執行 shutDwon 方法才有效。那麼 shutDownNow 方法呢?同樣的,也判斷了 state 變數必須大於等於 0 才能執行 interrupt 方法。保證執行緒池整體的狀態安全。

其實,這篇文章還是有點雜亂,東西有點多。但總體還是圍繞執行緒中斷來講的。

熟悉這些 API 的使用,對於併發程式設計來說,還是非常重要的。

再次總結

本文說了哪些東西:

  1. Java 中同步和鎖相關的 API 哪些可響應中斷,哪些不可響應中斷。總結下來就是 Lock 更靈活的對待中斷。
  2. Thread 類的兩個判斷中斷的方法,靜態方法會清除中斷狀態,成員方法則不會。
  3. 執行緒池的 shutDownNow 方法會根據任務中是否有響應中斷的 API 來決定是否立即中斷任務,如果有,則立即中斷,反之,等待任務完成。
  4. 執行緒池有趣的現象:Worker 初始化的時候,有一個變數設定成 -1 ,防止初始化的時候,使用者呼叫 shutDown 和 shutDownNow 方法。

相關文章