Java併發程式設計之執行緒篇之執行緒中斷(三)

AndyJennifer發表於2019-08-20

前言

在上篇文章Java併發程式設計之執行緒篇之執行緒簡介(二)中我們基本瞭解瞭如何建立一個執行緒並執行相應任務,但是並沒有提到如何中斷一個執行緒。例如:我們有一個下載程式執行緒,該執行緒在沒有下載成功之前是不會退出的,假如這個時候使用者不想下載了,那我們該如何中斷這個下載執行緒呢?下面我們就來學習如何正確的中斷一個執行緒吧。

對於過時的suspend()、resume()和stop()方法,這裡就不介紹了,有興趣的小夥伴可以查閱相關資料。

Java執行緒的中斷機制

當我們需要中斷某個執行緒時,看似我們只需要調一箇中斷方法(呼叫之後執行緒就不執行了)就行了。但是Java中並沒有提供一個實際的方法來中斷某個執行緒(不考慮過時的stop()方法),只提供了一箇中斷標誌位,來表示執行緒在執行期間已經被其他執行緒進行了中斷操作。也就是說執行緒只有通過自身來檢查這個標誌位,來判斷自己是否被中斷了。在Java中提供了三個方法來設定或判斷中斷標誌位,具體方法如下所示:

   //類方法,設定當前執行緒中標誌位
   public void interrupt() {
        if (this != Thread.currentThread())
            checkAccess();

        synchronized (blockerLock) {
            Interruptible b = blocker;
            if (b != null) {
                interrupt0();           // 設定中斷標誌位
                b.interrupt(this);
                return;
            }
        }
        interrupt0();//設定中斷標誌位
    }

    //靜態方法,判斷當前執行緒是否中斷,清除中斷標誌位
    public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }

    //類方法,判斷當前執行緒是否中斷,不清除中斷標誌位
    public boolean isInterrupted() {
        return isInterrupted(false);
    }
複製程式碼

在上述方法中,我們可以通過interrupt()來設定相應執行緒中斷標誌,通過Thread類靜態方法interrupted()和類方法isInterrupted()來判斷對應執行緒是否被其他執行緒中斷。

其中interrupted()與isInterrupted()方法的主要區別如下:

  • interrupted 判斷當前執行緒是否中斷(如果是中斷,則會清除中斷的狀態標誌,也就是如果中斷了執行緒,第一次呼叫這個方法返回true,第二次繼續呼叫則返回false。
  • isInterrupted 判斷執行緒是否已經中斷(不清除中斷的狀態標誌)。

使用interrupt()中斷執行緒

在上文中,我們瞭解了執行緒的如何設定中斷標誌位與如何判斷標誌位,那現在我們來使用interrupt()方法來中斷一個執行緒。先看下面這個例子。

class InterruptDemo {

    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 1000000; i++) {
                    System.out.println("i=" + i);
                }
            }
        });
        thread.start();
        try {
            Thread.sleep(2000);
            thread.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
//輸出結果:
i=593210
i=593211
i=593212
i=593213
i=593214
i=593215
i=593216

複製程式碼

執行上述程式碼,觀察輸出結果,我們發現執行緒並沒有被終止,原因是因為interrupt()方法只會設定執行緒中斷標誌位,並不會真正的中斷執行緒。也就是說我們只有自己來判斷執行緒是否終止。一般情況下,當我們檢查到執行緒被中斷(也就是執行緒標誌位為true)時,會丟擲一個InterruptedException異常,來中斷執行緒任務。具體程式碼如下:

class InterruptDemo {

    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    for (int i = 0; i < 1000000; i++) {
                        if (Thread.interrupted()) {
                            System.out.println("檢測到執行緒被中斷");
                            throw new InterruptedException();
                        }
                        System.out.println("i=" + i);
                    }
                } catch (InterruptedException e) {
                    //執行你自己的中斷邏輯
                    System.out.println("執行緒被中斷了,你自己判斷該如何處理吧");
                    e.printStackTrace();
                }
            }
        });
        thread.start();
        try {
            Thread.sleep(2000);
            thread.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
//輸出結果:
i=218626
i=218627
i=218628
i=218629
i=218630
檢測到執行緒被中斷
執行緒被中斷了,你自己判斷該如何處理吧
java.lang.InterruptedException
    at InterruptDemo$1.run(InterruptDemo.java:18)
    at java.base/java.lang.Thread.run(Thread.java:835)
複製程式碼

在上述程式碼中,我們通過線上程中判斷Thread.interrupted()來判斷執行緒是否中斷,當執行緒被中斷後,我們丟擲InterruptedException異常。然後通過try/catch來捕獲該異常來執行我們自己的中斷邏輯。當然我們也可以通過Thread.currentThread().isInterrupted()來判斷。這兩個方法的區別已經在上文介紹了,這裡就不過多的介紹了。

中斷執行緒的另一種方式

在上文中提到的使用interrupt()來中斷執行緒以外,我們還可以通過一個boolean來控制是否中斷執行緒。具體例子如下所示:

class InterruptDemo {

    public static void main(String[] args) {
        RunnableA runnableA = new RunnableA();
        Thread thread = new Thread(runnableA);
        thread.start();
        try {
            Thread.sleep(2000);
            runnableA.interruptThread();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    static class RunnableA implements Runnable {
        boolean isInterrupt;

        @Override
        public void run() {
            for (int i = 0; i < 1000000; i++) {
                if (!isInterrupt) {
                    System.out.println("i=" + i);
                } else {
                    System.out.println("執行緒結束執行了");
                    break;
                }
            }
        }

        void interruptThread() {
            isInterrupt = true;
        }
    }
}
//輸出結果:
i=240399
i=240400
i=240401
i=240402
i=240403
i=240404
i=240405
執行緒結束執行了
複製程式碼

上述程式碼中,我們通過判斷isInterrupt的值來判斷是否跳出迴圈。run()方法的結束,就標誌著執行緒已經執行完畢了。

最後

站在巨人的肩膀上,才能看的更遠~

  • 《Java併發程式設計的藝術》

相關文章