系列文章目錄java
線程中斷是一個很重要的概念,一般,取消一個任務的執行,最好的,同時也是最合理的方法,就是經過中斷。segmentfault
本篇咱們主要仍是經過源碼分析來看看中斷的概念。安全
本文的源碼基於JDK1.8less
java線程的中斷機制爲咱們提供了一個契機,使被中斷的線程可以有機會從當前的任務中跳脫出來。而中斷機制的最核心的兩個概念就是interrupt status
和 InterruptedException
。ide
java中對於中斷的大部分操做無外乎如下兩點:函數
InterruptedException
在java中,每個線程都有一箇中斷標誌位,表徵了當前線程是否處於被中斷狀態,咱們能夠把這個標識位理解成一個boolean類型的變量,當咱們中斷一個線程時,將該標識位設爲true,當咱們清除中斷狀態時,將其設置爲false, 其僞代碼以下:源碼分析
(注意,本文的僞代碼部分是我我的所寫,並不權威,只是幫助我本身理解寫的)this
// 注意,這是僞代碼!!! // 注意,這是僞代碼!!! // 注意,這是僞代碼!!! 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
)。code
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()
是咱們清除中斷的惟一方法。
咱們直接來看的源碼:
/** * 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: * <pre> * if (Thread.interrupted()) // Clears interrupted status! * throw new InterruptedException(); * </pre> * * @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 an <code>InterruptedException</code> with no detail message. */ public InterruptedException() { super(); } /** * Constructs an <code>InterruptedException</code> 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 <i>interrupted * status</i> 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 * <i>interrupted status</i> of the current thread is * cleared when this exception is thrown. */ public static native void sleep(long millis) throws InterruptedException;
與上面的wait方法一致,若是當前線程被中斷了,sleep方法會拋出InterruptedException,而且清除中斷狀態。
若是有其餘方法直接或間接的調用了這兩個方法,那他們天然也會在線程被中斷的時候拋出InterruptedException,而且清除中斷狀態。例如:
這裏值得注意的是,雖然這些方法會拋出InterruptedException,可是並不會終止當前線程的執行,當前線程能夠選擇忽略這個異常。
也就是說,不管是設置interrupt status
仍是拋出InterruptedException
,它們都是給當前線程的建議,當前線程能夠選擇採納或者不採納,它們並不會影響當前線程的執行。
至於在收到這些中斷的建議後,當前線程要怎麼處理,也徹底取決於當前線程。
上面咱們說了怎麼檢查(以及清除)一個線程的中斷狀態,提到當一個線程被中斷後,有一些方法會拋出InterruptedException。
下面咱們就來看看怎麼中斷一個線程。
要中斷一個線程,只需調用該線程的interrupt
方法,其源碼以下:
/** * Interrupts this thread. * * <p> 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. * * <p> 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}. * * <p> 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}. * * <p> 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. * * <p> If none of the previous conditions hold then this thread's interrupt * status will be set. </p> * * <p> 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. * * <p> 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();
咱們接着往下看:
/* ... * <p> 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的方法
Thread的方法
關於這一點,咱們上面在分析InterruptedException的時候已經分析過了。
這裏插一句,因爲上面這些方法在拋出InterruptedException異常後,會同時清除中斷標識位,所以當咱們此時不想或者沒法傳遞InterruptedException異常,也不對該異常作任何處理時,咱們最好經過再次調用interrupt來恢復中斷的狀態,以供上層調用者處理,這一點,咱們在逐行分析AQS源碼(二): 鎖的釋放的最後就說明過這種用法。
接下來的兩段註釋是關於NIO的,咱們暫時不看,直接看最後兩段:
/* ... * <p> If none of the previous conditions hold then this thread's interrupt * status will be set. </p> * * <p> Interrupting a thread that is not alive need not have any effect. */
這段話是說:
註釋看完了以後咱們再來看代碼部分,其實代碼部分很簡單,中間那段同步代碼塊是和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
,從而終止循環。
上面咱們分別介紹了怎樣終止一個處於阻塞狀態或運行狀態的線程,若是咱們將這兩種方法結合起來,那麼就能夠同時應對這兩種情況,從而可以終止任意一個存活的線程:
@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.interrupted方法同時會返回線程原來的中斷的狀態。
若是僅僅想查看線程當前的中斷狀態而不清除原來的狀態,則應該使用Thread#isInterrupted。
某些阻塞方法在拋出InterruptedException異常後,會同時清除中斷狀態。若不能對該異常作出處理也沒法向上層拋出,則應該經過再次調用interrupt方法恢復中斷狀態,以供上層處理,一般狀況下咱們都不該該屏蔽中斷請求。
中斷異常:
中斷異常通常是線程被中斷後,在一些block類型的方法(如wait
,sleep
,join
)中拋出。
咱們可使用Thread#interrupt中斷一個線程,被中斷的線程所受的影響爲如下兩種之一:
不管是中斷狀態的改變仍是InterruptedException
被拋出,這些都是當前線程能夠感知到的"建議",若是當前線程選擇忽略這些建議(例如簡單地catch住異常繼續執行),那麼中斷機制對於當前線程就沒有任何影響,就好像什麼也沒有發生同樣。
因此,中斷一個線程,只是傳遞了請求中斷的消息,並不會直接阻止一個線程的運行。
(完)
查看更多系列文章:系列文章目錄