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

Java多執行緒之interrupt()和執行緒終止方式

1. interrupt()說明

在介紹終止執行緒的方式之前,有必要先對interrupt()進行了解。
關於interrupt(),java的djk文件描述如下:http://docs.oracle.com/javase/7/docs/api/

Interrupts this thread.
Unless the current thread is interrupting itself, which is always permitted, the checkAccess method of this thread is invoked, which may cause a SecurityException to be thrown.

If this thread is blocked in an invocation of the wait(), wait(long), or wait(long, int) methods of the Object class, or of the join(), join(long), join(long, int), sleep(long), or sleep(long, int), methods of this class, then its interrupt status will be cleared and it will receive an InterruptedException.

If this thread is blocked in an I/O operation upon an interruptible channel then the channel will be closed, the thread's interrupt status will be set, and the thread will receive a ClosedByInterruptException.

If this thread is blocked in a Selector then the thread's interrupt status will be set and it will return immediately from the selection operation, possibly with a non-zero value, just as if the selector's wakeup method were invoked.

If none of the previous conditions hold then this thread's interrupt status will be set.

Interrupting a thread that is not alive need not have any effect.

大致意思是:

interrupt()的作用是中斷本執行緒。
本執行緒中斷自己是被允許的;其它執行緒呼叫本執行緒的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”。
中斷一個“已終止的執行緒”不會產生任何操作

2. 終止執行緒的方式

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

2.1 終止處於“阻塞狀態”的執行緒

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

@Override
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)迴圈體之內,就需要額外的新增退出處理。形式如下:

@Override
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)的操作。

2.2 終止處於“執行狀態”的執行緒

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

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

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

(02) 通過“額外新增標記”。
形式如下:

private volatile boolean flag= true;
protected void stopTask() {
    flag = false;
}

@Override
public void run() {
    while (flag) {
        // 執行任務...
    }
}

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

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

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

3. 終止執行緒的示例

interrupt()常常被用來終止“阻塞狀態”執行緒。參考下面示例:

// Demo1.java的原始碼
class MyThread extends Thread {
    
    public MyThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        try {  
            int i=0;
            while (!isInterrupted()) {
                Thread.sleep(100); // 休眠100ms
                i++;
                System.out.println(Thread.currentThread().getName()+" ("+this.getState()+") loop " + i);  
            }
        } catch (InterruptedException e) {  
            System.out.println(Thread.currentThread().getName() +" ("+this.getState()+") catch InterruptedException.");  
        }
    }
}

public class Demo1 {

    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.");  

            // 主執行緒休眠300ms,然後主執行緒給t1發“中斷”指令。
            Thread.sleep(300);
            t1.interrupt();
            System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted.");

            // 主執行緒休眠300ms,然後檢視t1的狀態。
            Thread.sleep(300);
            System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted now.");
        } catch (InterruptedException e) {  
            e.printStackTrace();
        }
    } 
}

 

執行結果

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 (TERMINATED) is interrupted now.

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

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

 

// Demo2.java的原始碼
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);  
        }
    }
}

public class Demo2 {

    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.");  

            // 主執行緒休眠300ms,然後主執行緒給t1發“中斷”指令。
            Thread.sleep(300);
            t1.interrupt();
            System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted.");

            // 主執行緒休眠300ms,然後檢視t1的狀態。
            Thread.sleep(300);
            System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted now.");
        } catch (InterruptedException e) {  
            e.printStackTrace();
        }
    } 
}

執行結果

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 (TIMED_WAITING) is interrupted now.
t1 (RUNNABLE) loop 6
t1 (RUNNABLE) loop 7
t1 (RUNNABLE) loop 8
t1 (RUNNABLE) loop 9
...

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

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

 

// Demo3.java的原始碼
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) {
            try {
                int i=0;
                while (flag) {
                    Thread.sleep(100); // 休眠100ms
                    i++;
                    System.out.println(Thread.currentThread().getName()+" ("+this.getState()+") loop " + i);  
                }
            } catch (InterruptedException ie) {  
                System.out.println(Thread.currentThread().getName() +" ("+this.getState()+") catch InterruptedException.");  
            }
        }  
    }
}

public class Demo3 {

    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.");  

            // 主執行緒休眠300ms,然後主執行緒給t1發“中斷”指令。
            Thread.sleep(300);
            t1.stopTask();
            System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted.");

            // 主執行緒休眠300ms,然後檢視t1的狀態。
            Thread.sleep(300);
            System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted now.");
        } catch (InterruptedException e) {  
            e.printStackTrace();
        }
    } 
}

執行結果

t1 (NEW) is new.
t1 (RUNNABLE) is started.
t1 (RUNNABLE) loop 1
t1 (RUNNABLE) loop 2
t1 (TIMED_WAITING) is interrupted.
t1 (RUNNABLE) loop 3
t1 (TERMINATED) is interrupted now.

4. interrupted() 和 isInterrupted()的區別

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

轉載:http://www.cnblogs.com/skywang12345/p/3479949.html