1. 程式人生 > >Java 多執行緒基礎(十)interrupt()和執行緒終止方式

Java 多執行緒基礎(十)interrupt()和執行緒終止方式

Java 多執行緒基礎(十)interrupt()和執行緒終止方式

一、interrupt() 介紹

interrupt() 定義在 Thread 類中,作用是中斷本執行緒。

本執行緒中斷自己是被允許的;其它執行緒呼叫本執行緒的 interrupt() 方法時,會通過 checkAccess() 檢查許可權。這有可能丟擲 SecurityException 異常。
如果本執行緒是處於阻塞狀態:呼叫執行緒的 wait() , wait(long) 或 wait(long, int) 會讓它進入等待(阻塞)狀態,或者呼叫執行緒的 join(),join(long),join(long, int),sleep(long),sleep(long, int) 也會讓它進入阻塞狀態。若執行緒在阻塞狀態時,呼叫了它的 interrupt() 方法,那麼它的“中斷狀態”會被清除並且會收到一個 InterruptedException 異常。例如,執行緒通過 wait() 進入阻塞狀態,此時通過 interrupt() 中斷該執行緒;呼叫 interrupt() 會立即將執行緒的中斷標記設為 true,但是由於執行緒處於阻塞狀態,所以該“中斷標記”會立即被清除為 “false”,同時,會產生一個 InterruptedException 的異常。
如果執行緒被阻塞在一個 Selector 選擇器中,那麼通過 interrupt() 中斷它時;執行緒的中斷標記會被設定為 true,並且它會立即從選擇操作中返回。
如果不屬於前面所說的情況,那麼通過 interrupt() 中斷執行緒時,它的中斷標記會被設定為 true。
中斷一個“已終止的執行緒”不會產生任何操作。

二、執行緒終止方式

Thread中的 stop() 和 suspend() 方法,由於固有的不安全性,已經建議不再使用!
下面,我先分別討論執行緒在“阻塞狀態”和“執行狀態”的終止方式,然後再總結出一個通用的方式。

(一)、終止處於“阻塞狀態”的執行緒.

通常,我們通過“中斷”方式終止處於“阻塞狀態”的執行緒。
當執行緒由於被呼叫了 sleep(),,wait(),join() 等方法而進入阻塞狀態;若此時呼叫執行緒的 interrupt() 將執行緒的中斷標記設為 true。由於處於阻塞狀態,中斷標記會被清除,同時產生一個InterruptedException 異常。將 InterruptedException 放在適當的位置就能終止執行緒,形式如下:

public void run() {
    try {
        while (true) {
            // 執行業務
        }
    } catch (InterruptedException ie) {  
        // 由於產生InterruptedException異常,退出while(true)迴圈,執行緒終止!
    }
}

說明:

在while(true)中不斷的執行業務程式碼,當執行緒處於阻塞狀態時,呼叫執行緒的 interrupt() 產生 InterruptedException 中斷。中斷的捕獲在 while(true) 之外,這樣就退出了 while(true) 迴圈!

注意:

對 InterruptedException 的捕獲務一般放在 while(true) 迴圈體的外面,這樣,在產生異常時就退出了 while(true) 迴圈。否則,InterruptedException 在 while(true) 迴圈體之內,就需要額外的新增退出處理。形式如下: 

public void run() {
    while (true) {
        try {
            // 執行任務...
        } catch (InterruptedException ie) {  
            // InterruptedException在while(true)迴圈體內。
            // 當執行緒產生了InterruptedException異常時,while(true)仍能繼續執行!需要手動退出
            break;
        }
    }
}

說明:

上面的 InterruptedException 異常的捕獲在 whle(true) 之內。當產生 InterruptedException 異常時,被 catch 處理之外,仍然在 while(true) 迴圈體內;要退出 while(true) 迴圈體,需要額外的執行退出while(true) 的操作。

(二)、終止處於“執行狀態”的執行緒

通常,我們通過“標記”方式終止處於“執行狀態”的執行緒。其中,包括“中斷標記”和“額外新增標記”。

1、通過“中斷標記”終止執行緒

public void run() {
    while (!isInterrupted()) {
        // 執行任務...
    }
}

說明:

isInterrupted() 是判斷執行緒的中斷標記是不是為 true。當執行緒處於執行狀態,並且我們需要終止它時;可以呼叫執行緒的 interrupt() 方法,使用執行緒的中斷標記為 true,即 isInterrupted() 會返回true。此時,就會退出while迴圈。
注意:interrupt() 並不會終止處於“執行狀態”的執行緒!它會將執行緒的中斷標記設為 true。

2、通過“額外新增標記”終止執行緒

private volatile boolean flag= true;
protected void stopTask() {
    flag = false;
}
public void run() {
    while (flag) {
        // 執行任務...
    }
}

說明:

執行緒中有一個 flag 標記,它的預設值是 true;並且我們提供 stopTask() 來設定 flag 標記。當我們需要終止該執行緒時,呼叫該執行緒的 stopTask() 方法就可以讓執行緒退出 while 迴圈。
注意:將 flag 定義為 volatile 型別,是為了保證 flag 的可見性。即其它執行緒通過 stopTask() 修改了 flag 之後,本執行緒能看到修改後的 flag 的值。

(三)、通過方式

綜合執行緒處於“阻塞狀態”和“執行狀態”的終止方式,比較通用的終止執行緒的形式如下:

public void run() {
    try {
        // 1. isInterrupted()保證,只要中斷標記為true就終止執行緒。
        while (!isInterrupted()) {
            // 執行任務...
        }
    } catch (InterruptedException ie) {  
        // 2. InterruptedException異常保證,當InterruptedException異常產生時,執行緒被終止。
    }
}
1、isInterrupted()保證,只要中斷標記為 true 就終止執行緒。
2、InterruptedException 異常保證,當 InterruptedException 異常產生時,執行緒被終止。

三、示例

public class InterruptTest {
    public static void main(String[] args) {
        try {
            Thread t1 = new MyThread("t1"); // 新建執行緒t1
            System.out.println(t1.getName() + "[" + t1.getState() + "] is new.");
            
            t1.start();// 啟動執行緒t1
            System.out.println(t1.getName() + "[" + t1.getState() + "] is started.");
            
            Thread.sleep(300);// 休眠300毫秒,然後主執行緒給t1發“中斷”指令,檢視t1狀態
            t1.interrupt();
            System.out.println(t1.getName() + "[" + t1.getState() + "] is interrupted.");
            
            Thread.sleep(300);// 休眠300毫秒,然後檢視t1狀態
            System.out.println(t1.getName() + "[" + t1.getState() + "] is interrupted now.");
        }catch(InterruptedException e) {
            e.printStackTrace();
        }
    }
}
class MyThread extends Thread{
    public MyThread(String name) {
        super(name);
    }
    @Override
    public void run() {
        try {
            int i = 0;
            while(!isInterrupted()) {
                Thread.sleep(100);// 休眠100毫秒
                ++i;
                System.out.println(Thread.currentThread().getName() + "[" + this.getState() + "] loop " + i);
            }
        }catch(InterruptedException e) {
            System.out.println(Thread.currentThread().getName() + "[" + this.getState() + "] catch InterruptedException");
        }
    }
}
// 執行結果
t1 [ NEW ] is new.
t1 [ RUNNABLE ] is started.
t1 [ RUNNABLE ] loop 1
t1 [ RUNNABLE ] loop 2
t1 [ RUNNABLE ] loop 3
t1 [ RUNNABLE ] catch InterruptedException
t1 [ TERMINATED ] is interrupted.
t1 [ TERMINATED ] is interrupted now.

說明:

①、主執行緒 main 中通過 new MyThread("t1") 建立執行緒 t1,之後通過 t1.start() 啟動執行緒 t1。
②、t1 啟動之後,會不斷的檢查它的中斷標記,如果中斷標記為“false”;則休眠 100ms。
③、t1 休眠之後,會切換到主執行緒main;主執行緒再次執行時,會執行t1.interrupt()中斷執行緒t1。t1收到中斷指令之後,會將t1的中斷標記設定“false”,而且會丟擲 InterruptedException 異常。在 t1 的 run() 方法中,是在迴圈體 while 之外捕獲的異常;因此迴圈被終止。

我們對上面的結果進行小小的修改,將run()方法中捕獲InterruptedException異常的程式碼塊移到while迴圈體內。

public class InterruptTest {
    public static void main(String[] args) {
        try {
            Thread t1 = new MyThread("t1"); // 新建執行緒t1
            System.out.println(t1.getName() + " [ " + t1.getState() + " ] is new.");
            
            t1.start();// 啟動執行緒t1
            System.out.println(t1.getName() + " [ " + t1.getState() + " ] is started.");
            
            Thread.sleep(300);// 休眠300毫秒,然後主執行緒給t1發“中斷”指令,檢視t1狀態
            t1.interrupt();
            System.out.println(t1.getName() + " [ " + t1.getState() + " ] is interrupted.");
            
            Thread.sleep(300);// 休眠300毫秒,然後檢視t1狀態
            System.out.println(t1.getName() + " [ " + t1.getState() + " ] is interrupted now.");
        }catch(InterruptedException e) {
            e.printStackTrace();
        }
        
    }
}
class MyThread extends Thread{
    public MyThread(String name) {
        super(name);
    }
    @Override
    public void run() {
        int i = 0;
        while(!isInterrupted()) {
            try {
                Thread.sleep(100); // 休眠100ms
            } catch (InterruptedException ie) {  
                System.out.println(Thread.currentThread().getName() +" [ "+this.getState()+" ] catch InterruptedException.");  
            }
            i++;
            System.out.println(Thread.currentThread().getName()+" [ "+this.getState()+" ] loop " + i);  
        }
    }
}
// 執行結果
t1 [ NEW ] is new.
t1 [ RUNNABLE ] is started.
t1 [ RUNNABLE ] loop 1
t1 [ RUNNABLE ] loop 2
t1 [ TIMED_WAITING ] is interrupted.
t1 [ RUNNABLE ] catch InterruptedException.
t1 [ RUNNABLE ] loop 3
t1 [ RUNNABLE ] loop 4
t1 [ RUNNABLE ] loop 5
t1 [ RUNNABLE ] loop 6
t1 [ RUNNABLE ] is interrupted now.
t1 [ RUNNABLE ] loop 7
...... // 無限迴圈

說明:

程式進入了死迴圈了。

這是因為,t1在“等待(阻塞)狀態”時,被 interrupt() 中斷;此時,會清除中斷標記(即 isInterrupted() 會返回 false),而且會丟擲 InterruptedException 異常(該異常在while迴圈體內被捕獲)。因此,t1理所當然的會進入死迴圈了。
解決該問題,需要我們在捕獲異常時,額外的進行退出 while 迴圈的處理。例如,在 MyThread 的 catch(InterruptedException) 中新增 break 或 return 就能解決該問題。

下面是通過“額外新增標記”的方式終止“狀態狀態”的執行緒的示例:

public class InterruptTest {
    public static void main(String[] args) {
        try {
            MyThread t1 = new MyThread("t1"); // 新建執行緒t1
            System.out.println(t1.getName() + " [ " + t1.getState() + " ] is new.");
            
            t1.start();// 啟動執行緒t1
            System.out.println(t1.getName() + " [ " + t1.getState() + " ] is started.");
            
            Thread.sleep(300);// 休眠300毫秒,然後主執行緒給t1發“中斷”指令,檢視t1狀態
            t1.stopTask();
            System.out.println(t1.getName() + " [ " + t1.getState() + " ] is interrupted.");
            
            Thread.sleep(300);// 休眠300毫秒,然後檢視t1狀態
            System.out.println(t1.getName() + " [ " + t1.getState() + " ] is interrupted now.");
        }catch(InterruptedException e) {
            e.printStackTrace();
        }
        
    }
}
class MyThread extends Thread{
    private volatile boolean flag = true;
    public void stopTask() {
        flag = false;
    }
    public MyThread(String name) {
        super(name);
    }
    @Override
    public void run() {
        synchronized (this) {
            int i = 0;
            while(flag) {
                try {
                    Thread.sleep(100); // 休眠100ms
                } catch (InterruptedException ie) {  
                    System.out.println(Thread.currentThread().getName() +" [ "+this.getState()+" ] catch InterruptedException.");  
                    break;
                }
                i++;
                System.out.println(Thread.currentThread().getName()+" [ "+this.getState()+" ] loop " + i);  
            }
        }
        
    }
}
// 執行結果
t1 [ NEW ] is new.
t1 [ RUNNABLE ] is started.
t1 [ RUNNABLE ] loop 1
t1 [ RUNNABLE ] loop 2
t1 [ RUNNABLE ] loop 3
t1 [ RUNNABLE ] is interrupted.
t1 [ TERMINATED ] is interrupted now.

四、interrupted() 和 isInterrupted()的區別

interrupted() 和 isInterrupted()都能夠用於檢測物件的“中斷標記”。
區別是,interrupted() 除了返回中斷標記之外,它還會清除中斷標記(即將中斷標記設為 false);而 isInterrupted() 僅僅返回中斷標