1. 程式人生 > >不學無數——InterruptedException異常

不學無數——InterruptedException異常

InterruptedException異常

在瞭解InterruptedException異常之前應該瞭解以下的幾個關於執行緒的一些基礎知識。

執行緒的狀態

執行緒在一定的條件下會發生狀態的改變,下面是執行緒的一些狀態

<center><img src="http://p9jfgo4wc.bkt.clouddn.com/thread.png"/></center>

  • 初始(NEW):新建一個執行緒的物件,還未呼叫start方法
  • 執行(RUNNABLE):java執行緒中將已經準備就緒(Ready)和正在執行中(Running)的兩種狀態都統稱為“Runnable”。準備就緒的執行緒會被放線上程池中等待被呼叫
  • 阻塞(BLOCKED):是因為某種的原因而放棄了CPU的使用權,暫時的停止了執行。直到執行緒進入準備就緒(Ready)狀態才會有機會轉到執行狀態
  • 等待(WAITING):該狀態的執行緒需要等待其他執行緒做出一些特定的動作(通知或者是中斷)
  • 超時等待(TIME_WAITING):該狀態和上面的等待不同,他可以在指定的時間內自行返回
  • 終止(TERMINATED):執行緒任務執行完畢

而InterruptedException異常從字面意思上就是中斷異常,那麼什麼是中斷呢?學習中斷之前我們先了解一下具體什麼是阻塞

執行緒阻塞

執行緒阻塞通常是指一個執行緒在執行過程中暫停,以等待某個條件的觸發。而什麼情況才會使得執行緒進入阻塞的狀態呢?

  • 等待阻塞:執行的執行緒執行wait()方法,該執行緒會釋放佔用的所有資源,JVM會把該執行緒放入“等待池”中。進入這個狀態後,是不能自動喚醒的,必須依靠其他執行緒呼叫notify()或notifyAll()方法才能被喚醒
  • 同步阻塞:執行的執行緒在獲取物件的同步鎖時,若該同步鎖被別的執行緒佔用,則JVM會把該執行緒放入“鎖池”中
  • 其他阻塞:執行的執行緒執行sleep()或join()方法,或者發出了I/O請求時,JVM會把該執行緒置為阻塞狀態。當sleep()狀態超時、join()等待執行緒終止或者超時、或者I/O處理完畢時,執行緒重新轉入就緒狀態

執行緒中斷

如果我們有一個執行中的軟體,例如是防毒軟體正在全盤查殺病毒,此時我們不想讓他防毒,這時候點選取消,那麼就是正在中斷一個執行的執行緒。

每一個執行緒都有一個boolean型別的標誌,此標誌意思是當前的請求是否請求中斷,預設為false。當一個執行緒A呼叫了執行緒B的interrupt方法時,那麼執行緒B的是否請求的中斷標誌變為true。而執行緒B可以呼叫方法檢測到此標誌的變化。

  1. 阻塞方法:如果執行緒B呼叫了阻塞方法,如果是否請求中斷標誌變為了true,那麼它會丟擲InterruptedException異常。丟擲異常的同時它會將執行緒B的是否請求中斷標誌置為false
  2. 非阻塞方法:可以通過執行緒B的isInterrupted方法進行檢測是否請求中斷標誌為true還是false,另外還有一個靜態的方法interrupted方法也可以檢測標誌。但是靜態方法它檢測完以後會自動的將是否請求中斷標誌位置為false。例如執行緒A呼叫了執行緒B的interrupt的方法,那麼如果此時執行緒B中用靜態interrupted方法進行檢測標誌位的變化的話,那麼第一次為true,第二次就為false。下面為具體的例子:
/**
 * @program: Test
 * @description:
 * @author: [email protected]
 * @create: 2018-07-31 15:43
 **/
public class InterrupTest implements Runnable{

    public void run(){
            try {
                while (true) {
                    Boolean a = Thread.currentThread().isInterrupted();
                    System.out.println("in run() - about to sleep for 20 seconds-------" + a);
                    Thread.sleep(20000);
                    System.out.println("in run() - woke up");
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();//如果不加上這一句,那麼cd將會都是false,因為在捕捉到InterruptedException異常的時候就會自動的中斷標誌置為了false
                Boolean c=Thread.interrupted();
                Boolean d=Thread.interrupted();
                System.out.println("c="+c);
                System.out.println("d="+d);
            }
    }
    public static void main(String[] args) {
        InterrupTest si = new InterrupTest();
        Thread t = new Thread(si);
        t.start();
        //主執行緒休眠2秒,從而確保剛才啟動的執行緒有機會執行一段時間
        try {
            Thread.sleep(2000);
        }catch(InterruptedException e){
            e.printStackTrace();
        }
        System.out.println("in main() - interrupting other thread");
        //中斷執行緒t
        t.interrupt();
        System.out.println("in main() - leaving");
    }
}

列印的引數如下:

in run() - about to sleep for 20 seconds-------false
in main() - interrupting other thread
in main() - leaving
c=true
d=false

現在知道執行緒可以檢測到自身的標誌位的變化,但是他只是一個標誌,如果執行緒本身不處理的話,那麼程式還是會執行下去,就好比,老師在學校叮囑要好好學習,具體什麼時候,如何好好學習還是看自身。

因此interrupt() 方法並不能立即中斷執行緒,該方法僅僅告訴執行緒外部已經有中斷請求,至於是否中斷還取決於執行緒自己

InterruptedException異常的處理

簡單的瞭解了什麼是阻塞和中斷以後,我們就該瞭解碰到InterruptedException異常該如何處理了。

不要不管不顧

有時候阻塞的方法丟擲InterruptedException異常並不合適,例如在Runnable中呼叫了可中斷的方法,因為你的程式是實現了Runnable介面,然後在重寫Runnable介面的run方法的時候,那麼子類丟擲的異常要小於等於父類的異常。而在Runnable中run方法是沒有拋異常的。所以此時是不能丟擲InterruptedException異常。如果此時你只是記錄日誌的話,那麼就是一個不負責任的做法,因為在捕獲InterruptedException異常的時候自動的將是否請求中斷標誌置為了false。至少在捕獲了InterruptedException異常之後,如果你什麼也不想做,那麼就將標誌重新置為true,以便棧中更高層的程式碼能知道中斷,並且對中斷作出響應。

捕獲到InterruptedException異常後恢復中斷狀態

public class TaskRunner implements Runnable {
    private BlockingQueue<Task> queue;
 
    public TaskRunner(BlockingQueue<Task> queue) { 
        this.queue = queue; 
    }
 
    public void run() { 
        try {
             while (true) {
                 Task task = queue.take(10, TimeUnit.SECONDS);
                 task.execute();
             }
         }
         catch (InterruptedException e) { 
             // Restore the interrupted status
             Thread.currentThread().interrupt();
         }
    }
}

參考文章