摘要:現(xiàn)在終止一個線程,基本上只能靠曲線救國式的中斷來實現(xiàn)。中斷機制的核心在于中斷狀態(tài)和異常中斷狀態(tài)設(shè)置一個中斷狀態(tài)清除一個中斷狀態(tài)方法同時會返回線程原來的中斷的狀態(tài)。中斷異常中斷異常一般是線程被中斷后,在一些類型的方法如中拋出。
前言
系列文章目錄
線程中斷是一個很重要的概念,通常,取消一個任務(wù)的執(zhí)行,最好的,同時也是最合理的方法,就是通過中斷。
本篇我們主要還是通過源碼分析來看看中斷的概念。
本文的源碼基于JDK1.8
Interrupt status & InterruptedExceptionjava線程的中斷機制為我們提供了一個契機,使被中斷的線程能夠有機會從當(dāng)前的任務(wù)中跳脫出來。而中斷機制的最核心的兩個概念就是interrupt status 和 InterruptedException。
java中對于中斷的大部分操作無外乎以下兩點:
設(shè)置或者清除中斷標(biāo)志位
拋出InterruptedException
interrupt status在java中,每一個線程都有一個中斷標(biāo)志位,表征了當(dāng)前線程是否處于被中斷狀態(tài),我們可以把這個標(biāo)識位理解成一個boolean類型的變量,當(dāng)我們中斷一個線程時,將該標(biāo)識位設(shè)為true,當(dāng)我們清除中斷狀態(tài)時,將其設(shè)置為false, 其偽代碼如下:
(注意,本文的偽代碼部分是我個人所寫,并不權(quán)威,只是幫助我自己理解寫的)
// 注意,這是偽代碼!??! // 注意,這是偽代碼?。。?// 注意,這是偽代碼?。?! public class Thread implements Runnable { private boolean interruptFlag; // 中斷標(biāo)志位 public boolean getInterruptFlag() { return this.interruptFlag; } public void setInterruptFlag(boolean flag) { this.interruptFlag = flag; } }
然而,在Thread線程類里面,并沒有類似中斷標(biāo)志位的屬性,但是提供了獲取中斷標(biāo)志位的接口:
/** * 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方法,該方法除了能夠返回當(dāng)前線程的中斷狀態(tài),還能根據(jù)ClearInterrupted參數(shù)來決定要不要重置中斷標(biāo)志位(reset操作相當(dāng)于上面的interruptFlag = false)。
Thread類提供了兩個public方法來使用該native方法:
public boolean isInterrupted() { return isInterrupted(false); } public static boolean interrupted() { return currentThread().isInterrupted(true); }
其中isInterrupted調(diào)用了isInterrupted(false), ClearInterrupted參數(shù)為false, 說明它僅僅返回線程實例的中斷狀態(tài),但是不會對現(xiàn)有的中斷狀態(tài)做任何改變,偽代碼可以是:
// 注意,這是偽代碼!??! // 注意,這是偽代碼?。?! // 注意,這是偽代碼?。?! public boolean isInterrupted() { return interruptFlag; //直接返回Thread實例的中斷狀態(tài) }
而interrupted是一個靜態(tài)方法,所以它可以由Thread類直接調(diào)用,自然就是作用于當(dāng)前正在執(zhí)行的線程,所以函數(shù)內(nèi)部使用了currentThread()方法,與isInterrupted()方法不同的是,它的ClearInterrupted參數(shù)為true,在返回線程中斷狀態(tài)的同時,重置了中斷標(biāo)識位,偽代碼可以是:
// 注意,這是偽代碼?。?! // 注意,這是偽代碼?。?! // 注意,這是偽代碼?。?! public static boolean interrupted() { Thread current = Thread.currentThread(); // 獲取當(dāng)前正在執(zhí)行的線程 boolean interruptFlag = current.getInterruptFlag(); // 獲取線程的中斷狀態(tài) current.setInterruptFlag(false); // 清除線程的中斷狀態(tài) return interruptFlag; //返回線程的中斷狀態(tài) }
可見,isInterrupted() 和 interrupted() 方法只涉及到中斷狀態(tài)的查詢,最多是多加一步重置中斷狀態(tài),并不牽涉到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;
該方法的注釋中提到,如果在有別的線程在當(dāng)前線程進入waiting狀態(tài)之前或者已經(jīng)進入waiting狀態(tài)之后中斷了當(dāng)前線程,該方法就會拋出InterruptedException,同時,異常拋出后,當(dāng)前線程的中斷狀態(tài)也會被清除。
(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方法一致,如果當(dāng)前線程被中斷了,sleep方法會拋出InterruptedException,并且清除中斷狀態(tài)。
如果有其他方法直接或間接的調(diào)用了這兩個方法,那他們自然也會在線程被中斷的時候拋出InterruptedException,并且清除中斷狀態(tài)。例如:
wait()
wait(long timeout, int nanos)
sleep(long millis, int nanos)
join()
join(long millis)
join(long millis, int nanos)
這里值得注意的是,雖然這些方法會拋出InterruptedException,但是并不會終止當(dāng)前線程的執(zhí)行,當(dāng)前線程可以選擇忽略這個異常。
也就是說,無論是設(shè)置interrupt status 還是拋出InterruptedException,它們都是給當(dāng)前線程的建議,當(dāng)前線程可以選擇采納或者不采納,它們并不會影響當(dāng)前線程的執(zhí)行。
至于在收到這些中斷的建議后,當(dāng)前線程要怎么處理,也完全取決于當(dāng)前線程。
interrupt上面我們說了怎么檢查(以及清除)一個線程的中斷狀態(tài),提到當(dāng)一個線程被中斷后,有一些方法會拋出InterruptedException。
下面我們就來看看怎么中斷一個線程。
要中斷一個線程,只需調(diào)用該線程的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. ... */
上面這段首先說明了這個函數(shù)的目的是中斷這個線程,這個this thread,當(dāng)然指的就是該方法所屬的線程對象所代表的線程。
接著說明了,一個線程總是被允許中斷自己,但是我們?nèi)绻胍谝粋€線程中中斷另一個線程的執(zhí)行,就需要先通過checkAccess()檢查權(quán)限。這有可能拋出SecurityException異常, 這段話用代碼體現(xiàn)為:
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}. ... */
上面這段是說,如果線程因為以下方法的調(diào)用而處于阻塞中,那么(調(diào)用了interrupt方法之后),線程的中斷標(biāo)志會被清除,并且收到一個InterruptedException:
Object的方法
wait()
wait(long)
wait(long, int)
Thread的方法
join()
join(long)
join(long, int)
sleep(long)
sleep(long, int)
關(guān)于這一點,我們上面在分析InterruptedException的時候已經(jīng)分析過了。
這里插一句,由于上面這些方法在拋出InterruptedException異常后,會同時清除中斷標(biāo)識位,因此當(dāng)我們此時不想或者無法傳遞InterruptedException異常,也不對該異常做任何處理時,我們最好通過再次調(diào)用interrupt來恢復(fù)中斷的狀態(tài),以供上層調(diào)用者處理,這一點,我們在逐行分析AQS源碼(二): 鎖的釋放的最后就說明過這種用法。
接下來的兩段注釋是關(guān)于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. */
這段話是說:
如果線程沒有因為上面的函數(shù)調(diào)用而進入阻塞狀態(tài)的話,那么中斷這個線程僅僅會設(shè)置它的中斷標(biāo)志位(而不會拋出InterruptedException)
中斷一個已經(jīng)終止的線程不會有任何影響。
注釋看完了之后我們再來看代碼部分,其實代碼部分很簡單,中間那段同步代碼塊是和NIO有關(guān)的,我們可以暫時不管,整個方法的核心調(diào)用就是interrupt0()方法,而它是一個native方法:
private native void interrupt0();
這個方法所做的事情很簡單:
Just to set the interrupt flag
所以,至此我們明白了,所謂“中斷一個線程”,其實并不是讓一個線程停止運行,僅僅是將線程的中斷標(biāo)志設(shè)為true, 或者在某些特定情況下拋出一個InterruptedException,它并不會直接將一個線程停掉,在被中斷的線程的角度看來,僅僅是自己的中斷標(biāo)志位被設(shè)為true了,或者自己所執(zhí)行的代碼中拋出了一個InterruptedException異常,僅此而已。
終止一個線程既然上面我們提到了,中斷一個線程并不會使得該線程停止執(zhí)行,那么我們該怎樣終止一個線程的執(zhí)行呢。早期的java中提供了stop()方法來停止一個線程,但是這個方法是不安全的,所以已經(jīng)被廢棄了。現(xiàn)在終止一個線程,基本上只能靠“曲線救國”式的中斷來實現(xiàn)。
終止處于阻塞狀態(tài)的線程前面我們說過,當(dāng)一個線程因為調(diào)用wait,sleep,join方法而進入阻塞狀態(tài)后,若在這時中斷這個線程,則這些方法將會拋出InterruptedException異常,我們可以利用這個異常,使線程跳出阻塞狀態(tài),從而終止線程。
@Override public void run() { while(true) { try { // do some task // blocked by calling wait/sleep/join } catch (InterruptedException ie) { // 如果該線程被中斷,則會拋出InterruptedException異常 // 我們通過捕獲這個異常,使得線程從block狀態(tài)退出 break; // 這里使用break, 可以使我們在線程中斷后退出死循環(huán),從而終止線程。 } } }終止處于運行狀態(tài)的線程
與中斷一個處于阻塞狀態(tài)所不同的是,中斷一個處于運行狀態(tài)的線程只會將該線程的中斷標(biāo)志位設(shè)為true, 而并不會拋出InterruptedException異常,為了能在運行過程中感知到線程已經(jīng)被中斷了,我們只能通過不斷地檢查中斷標(biāo)志位來實現(xiàn):
@Override public void run() { while (!isInterrupted()) { // do some task... } }
這里,我們每次循環(huán)都會先檢查中斷標(biāo)志位,只要當(dāng)前線程被中斷了,isInterrupted()方法就會返回true,從而終止循環(huán)。
終止一個Alive的線程上面我們分別介紹了怎樣終止一個處于阻塞狀態(tài)或運行狀態(tài)的線程,如果我們將這兩種方法結(jié)合起來,那么就可以同時應(yīng)對這兩種狀況,從而能夠終止任意一個存活的線程:
@Override public void run() { try { // 1. isInterrupted() 用于終止一個正在運行的線程。 while (!isInterrupted()) { // 執(zhí)行任務(wù)... } } catch (InterruptedException ie) { // 2. InterruptedException異常用于終止一個處于阻塞狀態(tài)的線程 } }
不過使用這兩者的組合一定要注意,wait,sleep,join等方法拋出InterruptedException有一個副作用: 清除當(dāng)前的中斷標(biāo)志位,所以不要在異常拋出后不做任何處理,而寄望于用isInterrupted()方法來判斷,因為中標(biāo)志位已經(jīng)被重置了,所以下面這種寫法是不對的:
@Override public void run() { //isInterrupted() 用于終止一個正在運行的線程。 while (!isInterrupted()) { try { // 執(zhí)行任務(wù)... } } catch (InterruptedException ie) { // 在這里不做任何處理,僅僅依靠isInterrupted檢測異常 } } }
這個方法中,在catch塊中我們檢測到異常后沒有使用break方法跳出循環(huán),而此時中斷狀態(tài)已經(jīng)被重置,當(dāng)我們再去調(diào)用isInterrupted,依舊會返回false, 故線程仍然會在while循環(huán)中執(zhí)行,無法被中斷。
總結(jié)Java沒有提供一種安全直接的方法來停止某個線程,但是提供了中斷機制。對于被中斷的線程,中斷只是一個建議,至于收到這個建議后線程要采取什么措施,完全由線程自己決定。
中斷機制的核心在于中斷狀態(tài)和InterruptedException異常
中斷狀態(tài):
設(shè)置一個中斷狀態(tài): Thread#interrupt
清除一個中斷狀態(tài): Thread.interrupted
Thread.interrupted方法同時會返回線程原來的中斷的狀態(tài)。
如果僅僅想查看線程當(dāng)前的中斷狀態(tài)而不清除原來的狀態(tài),則應(yīng)該使用Thread#isInterrupted。
某些阻塞方法在拋出InterruptedException異常后,會同時清除中斷狀態(tài)。若不能對該異常做出處理也無法向上層拋出,則應(yīng)該通過再次調(diào)用interrupt方法恢復(fù)中斷狀態(tài),以供上層處理,通常情況下我們都不應(yīng)該屏蔽中斷請求。
中斷異常:
中斷異常一般是線程被中斷后,在一些block類型的方法(如wait,sleep,join)中拋出。
我們可以使用Thread#interrupt中斷一個線程,被中斷的線程所受的影響為以下兩種之一:
若被中斷前,該線程處于非阻塞狀態(tài),那么該線程的中斷狀態(tài)被設(shè)為true, 除此之外,不會發(fā)生任何事。
若被中斷前,該線程處于阻塞狀態(tài)(調(diào)用了wait,sleep,join等方法),那么該線程將會立即從阻塞狀態(tài)中退出,并拋出一個InterruptedException異常,同時,該線程的中斷狀態(tài)被設(shè)為false, 除此之外,不會發(fā)生任何事。
無論是中斷狀態(tài)的改變還是InterruptedException被拋出,這些都是當(dāng)前線程可以感知到的"建議",如果當(dāng)前線程選擇忽略這些建議(例如簡單地catch住異常繼續(xù)執(zhí)行),那么中斷機制對于當(dāng)前線程就沒有任何影響,就好像什么也沒有發(fā)生一樣。
所以,中斷一個線程,只是傳遞了請求中斷的消息,并不會直接阻止一個線程的運行。
(完)
查看更多系列文章:系列文章目錄
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/76808.html
摘要:我們知道,這個函數(shù)將返回當(dāng)前正在執(zhí)行的線程的中斷狀態(tài),并清除它。注意,中斷對線程來說只是一個建議,一個線程被中斷只是其中斷狀態(tài)被設(shè)為線程可以選擇忽略這個中斷,中斷一個線程并不會影響線程的執(zhí)行。 前言 系列文章目錄 上一篇文章 我們逐行分析了獨占鎖的獲取操作, 本篇文章我們來看看獨占鎖的釋放。如果前面的鎖的獲取流程你已經(jīng)趟過一遍了, 那鎖的釋放部分就很簡單了, 這篇文章我們直接開始看...
摘要:前言中的線程是使用類實現(xiàn)的,在初學(xué)的時候就學(xué)過了,也在實踐中用過,不過一直沒從源碼的角度去看過它的實現(xiàn),今天從源碼的角度出發(fā),再次學(xué)習(xí),愿此后對的實踐更加得心應(yīng)手。如果一個線程已經(jīng)啟動并且尚未死亡,則該線程處于活動狀態(tài)。 showImg(https://segmentfault.com/img/remote/1460000017963014?w=1080&h=720); 前言 Java...
摘要:表示一個異步任務(wù)的結(jié)果,就是向線程池提交一個任務(wù)后,它會返回對應(yīng)的對象。它們分別提供兩個重要的功能阻塞當(dāng)前線程等待一段時間直到完成或者異常終止取消任務(wù)。此時,線程從中返回,然后檢查當(dāng)前的狀態(tài)已經(jīng)被改變,隨后退出循環(huán)。 0 引言 前段時間需要把一個C++的項目port到Java中,因此時隔三年后重新熟悉了下Java。由于需要一個通用的線程池,自然而然就想到了Executors。 用了...
摘要:為了避免一篇文章的篇幅過長,于是一些比較大的主題就都分成幾篇來講了,這篇文章是筆者所有文章的目錄,將會持續(xù)更新,以給大家一個查看系列文章的入口。 前言 大家好,筆者是今年才開始寫博客的,寫作的初衷主要是想記錄和分享自己的學(xué)習(xí)經(jīng)歷。因為寫作的時候發(fā)現(xiàn),為了弄懂一個知識,不得不先去了解另外一些知識,這樣以來,為了說明一個問題,就要把一系列知識都了解一遍,寫出來的文章就特別長。 為了避免一篇...
閱讀 2721·2021-11-11 16:55
閱讀 758·2021-09-04 16:40
閱讀 3144·2019-08-30 15:54
閱讀 2693·2019-08-30 15:54
閱讀 2475·2019-08-30 15:46
閱讀 459·2019-08-30 15:43
閱讀 3289·2019-08-30 11:11
閱讀 3043·2019-08-28 18:17