前面的幾篇文章主要介紹了線程的一些最基本的概念,包括線程的間的沖突及其解決辦法,以及線程間的協作機制。本篇主要來學習下Java中對線程中斷機制的實現。在我們的程序中經常會有一些不達到目的不會退出的線程,例如:我們有一個下載程序線程,該線程在沒有下載成功之前是不會退出的,若此時用戶覺得下載速度慢,不想下載了,這時就需要用到我們的線程中斷機制了,告訴線程,你不要繼續執行了,准備好退出吧。當然,線程在不同的狀態下遇到中斷會產生不同的響應,有點會拋出異常,有的則沒有變化,有的則會結束線程。本篇將從以下兩個方面來介紹Java中對線程中斷機制的具體實現:
- Java中對線程中斷所提供的API支持
- 線程在不同狀態下對於中斷所產生的反應
一、Java中對線程中斷所提供的API支持
在以前的jdk版本中,我們使用stop方法中斷線程,但是現在的jdk版本中已經不再推薦使用該方法了,反而由以下三個方法完成對線程中斷的支持。
public boolean isInterrupted()
public void interrupt()
public static boolean interrupted()
每個線程都一個狀態位用於標識當前線程對象是否是中斷狀態。isInterrupted是一個實例方法,主要用於判斷當前線程對象的中斷標志位是否被標記了,如果被標記了則返回true表示當前已經被中斷,否則返回false。我們也可以看看它的實現源碼:
public boolean isInterrupted() {
return isInterrupted(false);
}
private native boolean isInterrupted(boolean ClearInterrupted);
底層調用的本地方法isInterrupted,傳入一個boolean類型的參數,用於指定調用該方法之后是否需要清除該線程對象的中斷標識位。從這里我們也可以看出來,調用isInterrupted並不會清除線程對象的中斷標識位。
interrupt是一個實例方法,該方法用於設置當前線程對象的中斷標識位。
interrupted是一個靜態的方法,用於返回當前線程是否被中斷。
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
private native boolean isInterrupted(boolean ClearInterrupted);
該方法用於判斷當前線程是否被中斷,並且該方法調用結束的時候會清空中斷標識位。下面我們看看線程所處不同狀態下對於中斷操作的反應。
二、線程在不同狀態下對於中斷所產生的反應
線程一共6種狀態,分別是NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED(Thread類中有一個State枚舉類型列舉了線程的所有狀態)。下面我們就將把線程分別置於上述的不同種狀態,然后看看我們的中斷操作對它們的影響。
1、NEW和TERMINATED
線程的new狀態表示還未調用start方法,還未真正啟動。線程的terminated狀態表示線程已經運行終止。這兩個狀態下調用中斷方法來中斷線程的時候,Java認為毫無意義,所以並不會設置線程的中斷標識位,什么事也不會發生。例如:
public static void main(String[] args) throws InterruptedException {
Thread thread = new MyThread();
System.out.println(thread.getState());
thread.interrupt();
System.out.println(thread.isInterrupted());
}
輸出結果如下:
terminated狀態:
public static void main(String[] args) throws InterruptedException {
Thread thread = new MyThread();
thread.start();
thread.join();
System.out.println(thread.getState());
thread.interrupt();
System.out.println(thread.isInterrupted());
}
輸出結果如下:
從上述的兩個例子來看,對於處於new和terminated狀態的線程對於中斷是屏蔽的,也就是說中斷操作對這兩種狀態下的線程是無效的。
2、RUNNABLE
如果線程處於運行狀態,那么該線程的狀態就是RUNNABLE,但是不一定所有處於RUNNABLE狀態的線程都能獲得CPU運行,在某個時間段,只能由一個線程占用CPU,那么其余的線程雖然狀態是RUNNABLE,但是都沒有處於運行狀態。而我們處於RUNNABLE狀態的線程在遭遇中斷操作的時候只會設置該線程的中斷標志位,並不會讓線程實際中斷,想要發現本線程已經被要求中斷了則需要用程序去判斷。例如:
/*先定義一個線程類*/
public class MyThread extends Thread{
@Override
public void run(){
while(true){
//do something
}
}
}
/*main函數啟動線程*/
public static void main(String[] args) throws InterruptedException {
Thread thread = new MyThread();
thread.start();
System.out.println(thread.getState());
thread.interrupt();
Thread.sleep(1000);//等到thread線程被中斷之后
System.out.println(thread.isInterrupted());
System.out.println(thread.getState());
}
我們定義的線程始終循環做一些事情,主線程啟動該線程並輸出該線程的狀態,然后調用中斷方法中斷該線程並再次輸出該線程的狀態。總的輸出結果如下:
可以看到在我們啟動線程之后,線程狀態變為RUNNABLE,中斷之后輸出中斷標志,顯然中斷位已經被標記,但是當我們再次輸出線程狀態的時候發現,線程仍然處於RUNNABLE狀態。很顯然,處於RUNNBALE狀態下的線程即便遇到中斷操作,也只會設置中斷標志位並不會實際中斷線程運行。那么問題是,既然不能直接中斷線程,我要中斷標志有何用處?
這里其實Java將這種權力交給了我們的程序,Java給我們提供了一個中斷標志位,我們的程序可以通過if判斷中斷標志位是否被設置來中斷我們的程序而不是系統強制的中斷。例如:
/*修改MyThread類的run方法*/
public void run(){
while(true){
if (Thread.currentThread().isInterrupted()){
System.out.println("exit MyThread");
break;
}
}
}
線程一旦發現自己的中斷標志為被設置了,立馬跳出死循環。這樣的設計好處就在於給了我們程序更大的靈活性。
3、BLOCKED
當線程處於BLOCKED狀態說明該線程由於競爭某個對象的鎖失敗而被掛在了該對象的阻塞隊列上了。那么此時發起中斷操作不會對該線程產生任何影響,依然只是設置中斷標志位。例如:
/*自定義線程類*/
public class MyThread extends Thread{
public synchronized static void doSomething(){
while(true){
//do something
}
}
@Override
public void run(){
doSomething();
}
}
這里我們自定義了一個線程類,run方法中主要就做一件事情,調用一個有鎖的靜態方法,該方法內部是一個死循環(占用該鎖讓其他線程阻塞)。
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());
thread2.interrupt();
System.out.println(thread2.isInterrupted());
System.out.println(thread2.getState());
}
在我們的主線程中,我們定義了兩個線程並按照定義順序啟動他們,顯然thread1啟動后便占用MyThread類鎖,此后thread2在獲取鎖的時候一定失敗,自然被阻塞在阻塞隊列上,而我們對thread2進行中斷,輸出結果如下:
從輸出結果看來,thread2處於BLOCKED狀態,執行中斷操作之后,該線程仍然處於BLOCKED狀態,但是中斷標志位卻已被修改。這種狀態下的線程和處於RUNNABLE狀態下的線程是類似的,給了我們程序更大的靈活性去判斷和處理中斷。
4、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("i am waiting but facing interruptexception now");
}
}
}
}
我們定義了一個線程類,其中run方法讓當前線程阻塞到條件隊列上,並且針對InterruptedException 進行捕獲,如果遇到InterruptedException 異常則輸出一行信息。
/*main函數啟動該線程*/
public static void main(String[] args) throws InterruptedException {
Thread thread = new MyThread();
thread.start();
Thread.sleep(500);
System.out.println(thread.getState());
thread.interrupt();
Thread.sleep(1000);
System.out.println(thread.isInterrupted());
}
在main線程中我們啟動一個MyThread線程,然后對其進行中斷操作。
從運行結果看,當前程thread啟動之后就被掛起到該線程對象的條件隊列上,然后我們調用interrupt方法對該線程進行中斷,輸出了我們在catch中的輸出語句,顯然是捕獲了InterruptedException異常,接着就看到該線程的中斷標志位被清空。
綜上所述,我們分別介紹了不同種線程的不同狀態下對於中斷請求的反應。NEW和TERMINATED對於中斷操作幾乎是屏蔽的,RUNNABLE和BLOCKED類似,對於中斷操作只是設置中斷標志位並沒有強制終止線程,對於線程的終止權利依然在程序手中。WAITING/TIMED_WAITING狀態下的線程對於中斷操作是敏感的,他們會拋出異常並清空中斷標志位。