我們都知道,Java中停止一個線程不能用stop
,因為stop
會瞬間強行停止一個線程,且該線程持有的鎖並不能釋放。大家多習慣於用interrupt
,那么使用它又有什么需要注意的呢?
interrupt相關的方法
Java中和interrupt相關的方法有三個
public boolean isInterrupted()
public void interrupt()
public static boolean interrupted()
boolean isInterrupted()
每個線程都一個狀態位用於標識當前線程對象是否是中斷狀態。isInterrupted
主要用於判斷當前線程對象的中斷標志位是否被標記了,如果被標記了則返回true,表示當前已經被中斷,否則返回false。我們也可以看看它的實現源碼:
public boolean isInterrupted() {
return isInterrupted(false);
}
private native boolean isInterrupted(boolean ClearInterrupted);
底層調用的native方法isInterrupted
,傳入一個boolean類型的參數,用於指定調用該方法之后是否需要清除該線程的中斷標識位。從這里我們也可以看出來,調用isInterrupted()
並不會清除線程的中斷標識位。
void interrupt()
interrupt()
用於設置當前線程對象的中斷標識位,其源碼為:
public void interrupt() {
// 檢查當前線程是否有權限修改目標線程,如果沒有,則會拋出異常SecurityException
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();
}
blockerLock
和blocker
都和阻塞IO時產生的中斷相關,因此推測interrupt()
需要當阻塞IO操作執行完之后,才可以執行。
interrupt()
其實只是改變了一個標志位,對於線程本身的狀態並沒有影響。
boolean interrupted()
該方法是一個靜態的方法,用於返回當前線程是否被中斷,其源碼是:
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
需要注意的是:該方法調用結束的時候會清空中斷標識位
。
線程的狀態與中斷的關系
我們知道,Java中的線程一共6種狀態,分別是NEW
,RUNNABLE
,BLOCKED
,WAITING
,TIMED_WAITING
,TERMINATED
(Thread類中有一個State枚舉類型列舉了線程的所有狀態)。下面我們就將把線程分別置於上述的不同種狀態,然后看看中斷操作對它們的影響。
NEW和TERMINATED
NEW
狀態表示線程還未調用start()
方法,TERMINATED
狀態表示線程已經運行終止。
這兩個狀態下調用中斷方法來中斷線程的時候,Java認為毫無意義,所以並不會設置線程的中斷標識位。例如:
NEW
狀態:
public static void main(String[] args) {
Thread thread = new Thread();
System.out.println(thread.getState());
System.out.println(thread.isInterrupted());
thread.interrupt();
System.out.println(thread.isInterrupted());
}
輸出結果:
NEW
false
false
TERMINATED
狀態:
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread();
// 開始線程
thread.start();
// 等待線程結束
thread.join();
System.out.println(thread.getState());
System.out.println(thread.isInterrupted());
thread.interrupt();
System.out.println(thread.isInterrupted());
}
輸出結果:
TERMINATED
false
false
從上述的兩個例子來看,處於NEW
和TERMINATED
狀態的線程,對於中斷是屏蔽的,也就是說中斷操作對這兩種狀態下的線程是無效的。
RUNNABLE
處於RUNNABLE
狀態的線程,當中斷線程后,會修改其中斷標志位,但並不會影響線程本身。例如:
/**
* 自定義線程類
*/
public class MyThread extends Thread{
@Override
public void run(){
while(true){
// 什么都不做,就是空轉
}
}
public static void main(String[] args) {
Thread thread = new MyThread();
thread.start();
System.out.println(thread.getState());
System.out.println(thread.isInterrupted());
thread.interrupt();
System.out.println(thread.isInterrupted());
System.out.println(thread.getState());
}
}
結果為:
RUNNABLE
false
true
RUNNABLE
中斷標志位確實被改變了,但線程依舊繼續運行。那我們調用interrupt()
方法的意義在哪兒?
其實Java是將中斷線程的權利交給了我們自己的程序,通過中斷標志位,我們的程序可以通過boolean isInterrupted()
方法來判斷當前線程是否中斷,從而決定之后的操作。
我們可以在此基礎上,保證執行任務的原子性。例如修改MyThread
類的方法:
/**
* 自定義線程類
*/
public class MyThread extends Thread{
@Override
public void run(){
while(true){
if (this.isInterrupted()){
System.out.println("exit MyThread");
break;
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new MyThread();
thread.start();
System.out.println(thread.getState());
System.out.println(thread.isInterrupted());
thread.interrupt();
System.out.println(thread.isInterrupted());
thread.join();
System.out.println(thread.getState());
}
}
結果為:
RUNNABLE
false
true
exit MyThread
TERMINATED
BLOCKED
當線程處於BLOCKED
狀態,說明該線程由於競爭某個對象的鎖失敗而被掛在了該對象的阻塞隊列上了。
那么此時發起中斷操作不會對該線程產生任何影響,依然只是設置中斷標志位。例如:
/**
* 自定義線程類
*/
public class MyThread extends Thread{
public synchronized static void doSomething(){
while(true){
// 空轉
}
}
@Override
public void run(){
doSomething();
}
public static void main(String[] args) throws InterruptedException {
// 啟動兩個線程
Thread thread1 = new MyThread();
thread1.start();
Thread thread2 = new MyThread();
thread2.start();
Thread.sleep(1000);
System.out.println(thread1.getState());
System.out.println(thread2.getState());
System.out.println(thread2.isInterrupted());
thread2.interrupt();
System.out.println(thread2.isInterrupted());
System.out.println(thread2.getState());
}
}
結果為:
RUNNABLE
BLOCKED
false
true
BLOCKED
thread2處於BLOCKED
狀態,執行中斷操作之后,該線程仍然處於BLOCKED
狀態,但是中斷標志位卻已被修改。
這種狀態下的線程和處於RUNNABLE
狀態下的線程是類似的,給了我們程序更大的靈活性去判斷和處理中斷。
WAITING/TIMED_WAITING
這兩種狀態本質上是同一種狀態,只不過TIMED_WAITING
在等待一段時間后會自動釋放自己,而WAITING
則是無限期等待,需要其他線程調用類似notify
方法釋放自己。但是他們都是線程在運行的過程中由於缺少某些條件而被掛起在某個對象的等待隊列上。
當這些線程遇到中斷操作的時候,會拋出一個InterruptedException
異常,並清空中斷標志位。例如:
/**
* 自定義線程類
*/
public class MyThread extends Thread{
@Override
public void run(){
synchronized (this){
try {
wait();
} catch (InterruptedException e) {
System.out.println("catch InterruptedException");
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new MyThread();
thread.start();
Thread.sleep(1000);
System.out.println(thread.getState());
System.out.println(thread.isInterrupted());
thread.interrupt();
Thread.sleep(1000);
System.out.println(thread.isInterrupted());
}
}
結果為:
WAITING
false
catch InterruptedException
false
從運行結果看,當線程啟動之后就被掛起到該線程對象的等待隊列上,然后我們調用interrupt()
方法對該線程進行中斷,輸出了我們在catch中的輸出語句,顯然是捕獲了InterruptedException
異常,接着就看到該線程的中斷標志位被清空。
因此我們要么就在catch
語句中結束線程,否則就在catch
語句中加上this.interrupt();
,再次設置標志位,這樣也方便在之后的邏輯或者其他地方繼續判斷。
總結
我們介紹了線程在不同狀態下對於中斷請求的反應:
NEW
和TERMINATED
對於中斷操作幾乎是屏蔽的。RUNNABLE
和BLOCKED
類似,對於中斷操作只是設置中斷標志位並沒有強制終止線程,對於線程的終止權利依然在程序手中。WAITING
和TIMED_WAITING
狀態下的線程對於中斷操作是敏感的,他們會拋出異常並清空中斷標志位。
有興趣的話可以關注我的公眾號或者頭條號,說不定會有意外的驚喜。