摘要:現在終止一個線程,基本上只能靠曲線救國式的中斷來實現。中斷機制的核心在于中斷狀態和異常中斷狀態設置一個中斷狀態清除一個中斷狀態方法同時會返回線程原來的中斷的狀態。中斷異常中斷異常一般是線程被中斷后,在一些類型的方法如中拋出。
前言
系列文章目錄
線程中斷是一個很重要的概念,通常,取消一個任務的執行,最好的,同時也是最合理的方法,就是通過中斷。
本篇我們主要還是通過源碼分析來看看中斷的概念。
本文的源碼基于JDK1.8
Interrupt status & InterruptedExceptionjava線程的中斷機制為我們提供了一個契機,使被中斷的線程能夠有機會從當前的任務中跳脫出來。而中斷機制的最核心的兩個概念就是interrupt status 和 InterruptedException。
java中對于中斷的大部分操作無外乎以下兩點:
設置或者清除中斷標志位
拋出InterruptedException
interrupt status在java中,每一個線程都有一個中斷標志位,表征了當前線程是否處于被中斷狀態,我們可以把這個標識位理解成一個boolean類型的變量,當我們中斷一個線程時,將該標識位設為true,當我們清除中斷狀態時,將其設置為false, 其偽代碼如下:
(注意,本文的偽代碼部分是我個人所寫,并不權威,只是幫助我自己理解寫的)
// 注意,這是偽代碼!!! // 注意,這是偽代碼!!! // 注意,這是偽代碼!!! public class Thread implements Runnable { private boolean interruptFlag; // 中斷標志位 public boolean getInterruptFlag() { return this.interruptFlag; } public void setInterruptFlag(boolean flag) { this.interruptFlag = flag; } }
然而,在Thread線程類里面,并沒有類似中斷標志位的屬性,但是提供了獲取中斷標志位的接口:
/** * Tests if some Thread has been interrupted. The interrupted state * is reset or not based on the value of ClearInterrupted that is * passed. */ private native boolean isInterrupted(boolean ClearInterrupted);
這是一個native方法,同時也是一個private方法,該方法除了能夠返回當前線程的中斷狀態,還能根據ClearInterrupted參數來決定要不要重置中斷標志位(reset操作相當于上面的interruptFlag = false)。
Thread類提供了兩個public方法來使用該native方法:
public boolean isInterrupted() { return isInterrupted(false); } public static boolean interrupted() { return currentThread().isInterrupted(true); }
其中isInterrupted調用了isInterrupted(false), ClearInterrupted參數為false, 說明它僅僅返回線程實例的中斷狀態,但是不會對現有的中斷狀態做任何改變,偽代碼可以是:
// 注意,這是偽代碼!!! // 注意,這是偽代碼!!! // 注意,這是偽代碼!!! public boolean isInterrupted() { return interruptFlag; //直接返回Thread實例的中斷狀態 }
而interrupted是一個靜態方法,所以它可以由Thread類直接調用,自然就是作用于當前正在執行的線程,所以函數內部使用了currentThread()方法,與isInterrupted()方法不同的是,它的ClearInterrupted參數為true,在返回線程中斷狀態的同時,重置了中斷標識位,偽代碼可以是:
// 注意,這是偽代碼!!! // 注意,這是偽代碼!!! // 注意,這是偽代碼!!! public static boolean interrupted() { Thread current = Thread.currentThread(); // 獲取當前正在執行的線程 boolean interruptFlag = current.getInterruptFlag(); // 獲取線程的中斷狀態 current.setInterruptFlag(false); // 清除線程的中斷狀態 return interruptFlag; //返回線程的中斷狀態 }
可見,isInterrupted() 和 interrupted() 方法只涉及到中斷狀態的查詢,最多是多加一步重置中斷狀態,并不牽涉到InterruptedException。
不過值得一提的是,在我們能使用到的public方法中,interrupted()是我們清除中斷的唯一方法。
InterruptedException我們直接來看的源碼:
/** * Thrown when a thread is waiting, sleeping, or otherwise occupied, * and the thread is interrupted, either before or during the activity. * Occasionally a method may wish to test whether the current * thread has been interrupted, and if so, to immediately throw * this exception. The following code can be used to achieve * this effect: ** if (Thread.interrupted()) // Clears interrupted status! * throw new InterruptedException(); ** * @author Frank Yellin * @see java.lang.Object#wait() * @see java.lang.Object#wait(long) * @see java.lang.Object#wait(long, int) * @see java.lang.Thread#sleep(long) * @see java.lang.Thread#interrupt() * @see java.lang.Thread#interrupted() * @since JDK1.0 */ public class InterruptedException extends Exception { private static final long serialVersionUID = 6700697376100628473L; /** * Constructs anInterruptedException
with no detail message. */ public InterruptedException() { super(); } /** * Constructs anInterruptedException
with the * specified detail message. * * @param s the detail message. */ public InterruptedException(String s) { super(s); } }
上面的注釋是說,在線程處于“waiting, sleeping”甚至是正在運行的過程中,如果被中斷了,就可以拋出該異常,我們先來回顧一下我們前面遇到過的拋出InterruptedException異常的例子:
(1) wait(long timeout)方法中的InterruptedException
/* * * @param timeout the maximum time to wait in milliseconds. * @throws IllegalArgumentException if the value of timeout is * negative. * @throws IllegalMonitorStateException if the current thread is not * the owner of the object"s monitor. * @throws InterruptedException if any thread interrupted the * current thread before or while the current thread * was waiting for a notification. The interrupted * status of the current thread is cleared when * this exception is thrown. * @see java.lang.Object#notify() * @see java.lang.Object#notifyAll() */ public final native void wait(long timeout) throws InterruptedException;
該方法的注釋中提到,如果在有別的線程在當前線程進入waiting狀態之前或者已經進入waiting狀態之后中斷了當前線程,該方法就會拋出InterruptedException,同時,異常拋出后,當前線程的中斷狀態也會被清除。
(2) sleep(long millis)方法中的InterruptedException
/* @param millis * the length of time to sleep in milliseconds * * @throws IllegalArgumentException * if the value of {@code millis} is negative * * @throws InterruptedException * if any thread has interrupted the current thread. The * interrupted status of the current thread is * cleared when this exception is thrown. */ public static native void sleep(long millis) throws InterruptedException;
與上面的wait方法一致,如果當前線程被中斷了,sleep方法會拋出InterruptedException,并且清除中斷狀態。
如果有其他方法直接或間接的調用了這兩個方法,那他們自然也會在線程被中斷的時候拋出InterruptedException,并且清除中斷狀態。例如:
wait()
wait(long timeout, int nanos)
sleep(long millis, int nanos)
join()
join(long millis)
join(long millis, int nanos)
這里值得注意的是,雖然這些方法會拋出InterruptedException,但是并不會終止當前線程的執行,當前線程可以選擇忽略這個異常。
也就是說,無論是設置interrupt status 還是拋出InterruptedException,它們都是給當前線程的建議,當前線程可以選擇采納或者不采納,它們并不會影響當前線程的執行。
至于在收到這些中斷的建議后,當前線程要怎么處理,也完全取決于當前線程。
interrupt上面我們說了怎么檢查(以及清除)一個線程的中斷狀態,提到當一個線程被中斷后,有一些方法會拋出InterruptedException。
下面我們就來看看怎么中斷一個線程。
要中斷一個線程,只需調用該線程的interrupt方法,其源碼如下:
/** * Interrupts this thread. * *Unless the current thread is interrupting itself, which is * always permitted, the {@link #checkAccess() checkAccess} method * of this thread is invoked, which may cause a {@link * SecurityException} to be thrown. * *
If this thread is blocked in an invocation of the {@link * Object#wait() wait()}, {@link Object#wait(long) wait(long)}, or {@link * Object#wait(long, int) wait(long, int)} methods of the {@link Object} * class, or of the {@link #join()}, {@link #join(long)}, {@link * #join(long, int)}, {@link #sleep(long)}, or {@link #sleep(long, int)}, * methods of this class, then its interrupt status will be cleared and it * will receive an {@link InterruptedException}. * *
If this thread is blocked in an I/O operation upon an {@link * java.nio.channels.InterruptibleChannel InterruptibleChannel} * then the channel will be closed, the thread"s interrupt * status will be set, and the thread will receive a {@link * java.nio.channels.ClosedByInterruptException}. * *
If this thread is blocked in a {@link java.nio.channels.Selector} * then the thread"s interrupt status will be set and it will return * immediately from the selection operation, possibly with a non-zero * value, just as if the selector"s {@link * java.nio.channels.Selector#wakeup wakeup} method were invoked. * *
If none of the previous conditions hold then this thread"s interrupt * status will be set.
* *Interrupting a thread that is not alive need not have any effect. * * @throws SecurityException * if the current thread cannot modify this thread * * @revised 6.0 * @spec JSR-51 */ public void interrupt() { if (this != Thread.currentThread()) checkAccess(); synchronized (blockerLock) { Interruptible b = blocker; if (b != null) { interrupt0(); // Just to set the interrupt flag b.interrupt(this); return; } } interrupt0(); }
上面的注釋很長,我們一段一段來看:
/** * Interrupts this thread. * *Unless the current thread is interrupting itself, which is * always permitted, the {@link #checkAccess() checkAccess} method * of this thread is invoked, which may cause a {@link * SecurityException} to be thrown. ... */
上面這段首先說明了這個函數的目的是中斷這個線程,這個this thread,當然指的就是該方法所屬的線程對象所代表的線程。
接著說明了,一個線程總是被允許中斷自己,但是我們如果想要在一個線程中中斷另一個線程的執行,就需要先通過checkAccess()檢查權限。這有可能拋出SecurityException異常, 這段話用代碼體現為:
if (this != Thread.currentThread()) checkAccess();
我們接著往下看:
/* ... *If this thread is blocked in an invocation of the {@link * Object#wait() wait()}, {@link Object#wait(long) wait(long)}, or {@link * Object#wait(long, int) wait(long, int)} methods of the {@link Object} * class, or of the {@link #join()}, {@link #join(long)}, {@link * #join(long, int)}, {@link #sleep(long)}, or {@link #sleep(long, int)}, * methods of this class, then its interrupt status will be cleared and it * will receive an {@link InterruptedException}. ... */
上面這段是說,如果線程因為以下方法的調用而處于阻塞中,那么(調用了interrupt方法之后),線程的中斷標志會被清除,并且收到一個InterruptedException:
Object的方法
wait()
wait(long)
wait(long, int)
Thread的方法
join()
join(long)
join(long, int)
sleep(long)
sleep(long, int)
關于這一點,我們上面在分析InterruptedException的時候已經分析過了。
這里插一句,由于上面這些方法在拋出InterruptedException異常后,會同時清除中斷標識位,因此當我們此時不想或者無法傳遞InterruptedException異常,也不對該異常做任何處理時,我們最好通過再次調用interrupt來恢復中斷的狀態,以供上層調用者處理,這一點,我們在逐行分析AQS源碼(二): 鎖的釋放的最后就說明過這種用法。
接下來的兩段注釋是關于NIO的,我們暫時不看,直接看最后兩段:
/* ... *If none of the previous conditions hold then this thread"s interrupt * status will be set.
* *Interrupting a thread that is not alive need not have any effect. */
這段話是說:
如果線程沒有因為上面的函數調用而進入阻塞狀態的話,那么中斷這個線程僅僅會設置它的中斷標志位(而不會拋出InterruptedException)
中斷一個已經終止的線程不會有任何影響。
注釋看完了之后我們再來看代碼部分,其實代碼部分很簡單,中間那段同步代碼塊是和NIO有關的,我們可以暫時不管,整個方法的核心調用就是interrupt0()方法,而它是一個native方法:
private native void interrupt0();
這個方法所做的事情很簡單:
Just to set the interrupt flag
所以,至此我們明白了,所謂“中斷一個線程”,其實并不是讓一個線程停止運行,僅僅是將線程的中斷標志設為true, 或者在某些特定情況下拋出一個InterruptedException,它并不會直接將一個線程停掉,在被中斷的線程的角度看來,僅僅是自己的中斷標志位被設為true了,或者自己所執行的代碼中拋出了一個InterruptedException異常,僅此而已。
終止一個線程既然上面我們提到了,中斷一個線程并不會使得該線程停止執行,那么我們該怎樣終止一個線程的執行呢。早期的java中提供了stop()方法來停止一個線程,但是這個方法是不安全的,所以已經被廢棄了。現在終止一個線程,基本上只能靠“曲線救國”式的中斷來實現。
終止處于阻塞狀態的線程前面我們說過,當一個線程因為調用wait,sleep,join方法而進入阻塞狀態后,若在這時中斷這個線程,則這些方法將會拋出InterruptedException異常,我們可以利用這個異常,使線程跳出阻塞狀態,從而終止線程。
@Override public void run() { while(true) { try { // do some task // blocked by calling wait/sleep/join } catch (InterruptedException ie) { // 如果該線程被中斷,則會拋出InterruptedException異常 // 我們通過捕獲這個異常,使得線程從block狀態退出 break; // 這里使用break, 可以使我們在線程中斷后退出死循環,從而終止線程。 } } }終止處于運行狀態的線程
與中斷一個處于阻塞狀態所不同的是,中斷一個處于運行狀態的線程只會將該線程的中斷標志位設為true, 而并不會拋出InterruptedException異常,為了能在運行過程中感知到線程已經被中斷了,我們只能通過不斷地檢查中斷標志位來實現:
@Override public void run() { while (!isInterrupted()) { // do some task... } }
這里,我們每次循環都會先檢查中斷標志位,只要當前線程被中斷了,isInterrupted()方法就會返回true,從而終止循環。
終止一個Alive的線程上面我們分別介紹了怎樣終止一個處于阻塞狀態或運行狀態的線程,如果我們將這兩種方法結合起來,那么就可以同時應對這兩種狀況,從而能夠終止任意一個存活的線程:
@Override public void run() { try { // 1. isInterrupted() 用于終止一個正在運行的線程。 while (!isInterrupted()) { // 執行任務... } } catch (InterruptedException ie) { // 2. InterruptedException異常用于終止一個處于阻塞狀態的線程 } }
不過使用這兩者的組合一定要注意,wait,sleep,join等方法拋出InterruptedException有一個副作用: 清除當前的中斷標志位,所以不要在異常拋出后不做任何處理,而寄望于用isInterrupted()方法來判斷,因為中標志位已經被重置了,所以下面這種寫法是不對的:
@Override public void run() { //isInterrupted() 用于終止一個正在運行的線程。 while (!isInterrupted()) { try { // 執行任務... } } catch (InterruptedException ie) { // 在這里不做任何處理,僅僅依靠isInterrupted檢測異常 } } }
這個方法中,在catch塊中我們檢測到異常后沒有使用break方法跳出循環,而此時中斷狀態已經被重置,當我們再去調用isInterrupted,依舊會返回false, 故線程仍然會在while循環中執行,無法被中斷。
總結Java沒有提供一種安全直接的方法來停止某個線程,但是提供了中斷機制。對于被中斷的線程,中斷只是一個建議,至于收到這個建議后線程要采取什么措施,完全由線程自己決定。
中斷機制的核心在于中斷狀態和InterruptedException異常
中斷狀態:
設置一個中斷狀態: Thread#interrupt
清除一個中斷狀態: Thread.interrupted
Thread.interrupted方法同時會返回線程原來的中斷的狀態。
如果僅僅想查看線程當前的中斷狀態而不清除原來的狀態,則應該使用Thread#isInterrupted。
某些阻塞方法在拋出InterruptedException異常后,會同時清除中斷狀態。若不能對該異常做出處理也無法向上層拋出,則應該通過再次調用interrupt方法恢復中斷狀態,以供上層處理,通常情況下我們都不應該屏蔽中斷請求。
中斷異常:
中斷異常一般是線程被中斷后,在一些block類型的方法(如wait,sleep,join)中拋出。
我們可以使用Thread#interrupt中斷一個線程,被中斷的線程所受的影響為以下兩種之一:
若被中斷前,該線程處于非阻塞狀態,那么該線程的中斷狀態被設為true, 除此之外,不會發生任何事。
若被中斷前,該線程處于阻塞狀態(調用了wait,sleep,join等方法),那么該線程將會立即從阻塞狀態中退出,并拋出一個InterruptedException異常,同時,該線程的中斷狀態被設為false, 除此之外,不會發生任何事。
無論是中斷狀態的改變還是InterruptedException被拋出,這些都是當前線程可以感知到的"建議",如果當前線程選擇忽略這些建議(例如簡單地catch住異常繼續執行),那么中斷機制對于當前線程就沒有任何影響,就好像什么也沒有發生一樣。
所以,中斷一個線程,只是傳遞了請求中斷的消息,并不會直接阻止一個線程的運行。
(完)
查看更多系列文章:系列文章目錄
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/76808.html
摘要:我們知道,這個函數將返回當前正在執行的線程的中斷狀態,并清除它。注意,中斷對線程來說只是一個建議,一個線程被中斷只是其中斷狀態被設為線程可以選擇忽略這個中斷,中斷一個線程并不會影響線程的執行。 前言 系列文章目錄 上一篇文章 我們逐行分析了獨占鎖的獲取操作, 本篇文章我們來看看獨占鎖的釋放。如果前面的鎖的獲取流程你已經趟過一遍了, 那鎖的釋放部分就很簡單了, 這篇文章我們直接開始看...
摘要:前言中的線程是使用類實現的,在初學的時候就學過了,也在實踐中用過,不過一直沒從源碼的角度去看過它的實現,今天從源碼的角度出發,再次學習,愿此后對的實踐更加得心應手。如果一個線程已經啟動并且尚未死亡,則該線程處于活動狀態。 showImg(https://segmentfault.com/img/remote/1460000017963014?w=1080&h=720); 前言 Java...
摘要:表示一個異步任務的結果,就是向線程池提交一個任務后,它會返回對應的對象。它們分別提供兩個重要的功能阻塞當前線程等待一段時間直到完成或者異常終止取消任務。此時,線程從中返回,然后檢查當前的狀態已經被改變,隨后退出循環。 0 引言 前段時間需要把一個C++的項目port到Java中,因此時隔三年后重新熟悉了下Java。由于需要一個通用的線程池,自然而然就想到了Executors。 用了...
摘要:為了避免一篇文章的篇幅過長,于是一些比較大的主題就都分成幾篇來講了,這篇文章是筆者所有文章的目錄,將會持續更新,以給大家一個查看系列文章的入口。 前言 大家好,筆者是今年才開始寫博客的,寫作的初衷主要是想記錄和分享自己的學習經歷。因為寫作的時候發現,為了弄懂一個知識,不得不先去了解另外一些知識,這樣以來,為了說明一個問題,就要把一系列知識都了解一遍,寫出來的文章就特別長。 為了避免一篇...
閱讀 2642·2021-11-11 16:55
閱讀 680·2021-09-04 16:40
閱讀 3077·2019-08-30 15:54
閱讀 2615·2019-08-30 15:54
閱讀 2403·2019-08-30 15:46
閱讀 403·2019-08-30 15:43
閱讀 3227·2019-08-30 11:11
閱讀 2982·2019-08-28 18:17