Java多线程 interrupt()和线程终止方式

 

概要

本章,会对线程的interrupt()中断和终止方式进行介绍。涉及到的内容包括:
1. interrupt()说明
2. 终止线程的方式
  2.1 终止处于“阻塞状态”的线程
  2.2 终止处于“运行状态”的线程
3. 终止线程的示例
4. interrupted() 和 isInterrupted()的区别

转载请注明出处:http://www.cnblogs.com/skywang12345/p/3479949.html

 

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()常常被用来终止“阻塞状态”线程。参考下面示例:

复制代码
 1 // Demo1.java的源码
 2 class MyThread extends Thread {
 3     
 4     public MyThread(String name) {
 5         super(name);
 6     }
 7 
 8     @Override
 9     public void run() {
10         try {  
11             int i=0;
12             while (!isInterrupted()) {
13                 Thread.sleep(100); // 休眠100ms
14                 i++;
15                 System.out.println(Thread.currentThread().getName()+" ("+this.getState()+") loop " + i);  
16             }
17         } catch (InterruptedException e) {  
18             System.out.println(Thread.currentThread().getName() +" ("+this.getState()+") catch InterruptedException.");  
19         }
20     }
21 }
22 
23 public class Demo1 {
24 
25     public static void main(String[] args) {  
26         try {  
27             Thread t1 = new MyThread("t1");  // 新建“线程t1”
28             System.out.println(t1.getName() +" ("+t1.getState()+") is new.");  
29 
30             t1.start();                      // 启动“线程t1”
31             System.out.println(t1.getName() +" ("+t1.getState()+") is started.");  
32 
33             // 主线程休眠300ms,然后主线程给t1发“中断”指令。
34             Thread.sleep(300);
35             t1.interrupt();
36             System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted.");
37 
38             // 主线程休眠300ms,然后查看t1的状态。
39             Thread.sleep(300);
40             System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted now.");
41         } catch (InterruptedException e) {  
42             e.printStackTrace();
43         }
44     } 
45 }
复制代码

运行结果

复制代码
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循环体内。

复制代码
 1 // Demo2.java的源码
 2 class MyThread extends Thread {
 3     
 4     public MyThread(String name) {
 5         super(name);
 6     }
 7 
 8     @Override
 9     public void run() {
10         int i=0;
11         while (!isInterrupted()) {
12             try {
13                 Thread.sleep(100); // 休眠100ms
14             } catch (InterruptedException ie) {  
15                 System.out.println(Thread.currentThread().getName() +" ("+this.getState()+") catch InterruptedException.");  
16             }
17             i++;
18             System.out.println(Thread.currentThread().getName()+" ("+this.getState()+") loop " + i);  
19         }
20     }
21 }
22 
23 public class Demo2 {
24 
25     public static void main(String[] args) {  
26         try {  
27             Thread t1 = new MyThread("t1");  // 新建“线程t1”
28             System.out.println(t1.getName() +" ("+t1.getState()+") is new.");  
29 
30             t1.start();                      // 启动“线程t1”
31             System.out.println(t1.getName() +" ("+t1.getState()+") is started.");  
32 
33             // 主线程休眠300ms,然后主线程给t1发“中断”指令。
34             Thread.sleep(300);
35             t1.interrupt();
36             System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted.");
37 
38             // 主线程休眠300ms,然后查看t1的状态。
39             Thread.sleep(300);
40             System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted now.");
41         } catch (InterruptedException e) {  
42             e.printStackTrace();
43         }
44     } 
45 }
复制代码

运行结果

复制代码
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就能解决该问题。

下面是通过“额外添加标记”的方式终止“状态状态”的线程的示例:

复制代码
 1 // Demo3.java的源码
 2 class MyThread extends Thread {
 3 
 4     private volatile boolean flag= true;
 5     public void stopTask() {
 6         flag = false;
 7     }
 8     
 9     public MyThread(String name) {
10         super(name);
11     }
12 
13     @Override
14     public void run() {
15         synchronized(this) {
16             try {
17                 int i=0;
18                 while (flag) {
19                     Thread.sleep(100); // 休眠100ms
20                     i++;
21                     System.out.println(Thread.currentThread().getName()+" ("+this.getState()+") loop " + i);  
22                 }
23             } catch (InterruptedException ie) {  
24                 System.out.println(Thread.currentThread().getName() +" ("+this.getState()+") catch InterruptedException.");  
25             }
26         }  
27     }
28 }
29 
30 public class Demo3 {
31 
32     public static void main(String[] args) {  
33         try {  
34             MyThread t1 = new MyThread("t1");  // 新建“线程t1”
35             System.out.println(t1.getName() +" ("+t1.getState()+") is new.");  
36 
37             t1.start();                      // 启动“线程t1”
38             System.out.println(t1.getName() +" ("+t1.getState()+") is started.");  
39 
40             // 主线程休眠300ms,然后主线程给t1发“中断”指令。
41             Thread.sleep(300);
42             t1.stopTask();
43             System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted.");
44 
45             // 主线程休眠300ms,然后查看t1的状态。
46             Thread.sleep(300);
47             System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted now.");
48         } catch (InterruptedException e) {  
49             e.printStackTrace();
50         }
51     } 
52 }
复制代码

运行结果

复制代码
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()仅仅返回中断标记。




线程中断

使用 interrupt()中断线程

当一个线程运行时,另一个线程可以调用对应的 Thread 对象的 interrupt()方法来中断它,该方法只是在目标线程中设置一个标志,表示它已经被中断,并立即返回。这里需要注意的是,如果只是单纯的调用 interrupt()方法,线程并没有实际被中断,会继续往下执行。

下面一段代码演示了休眠线程的中断:

public class SleepInterrupt extends Object implements Runnable{  
    public void run(){  
        try{  
            System.out.println("in run() - about to sleep for 20 seconds");  
            Thread.sleep(20000);  
            System.out.println("in run() - woke up");  
        }catch(InterruptedException e){  
            System.out.println("in run() - interrupted while sleeping");  
            //处理完中断异常后,返回到run()方法人口,  
            //如果没有return,线程不会实际被中断,它会继续打印下面的信息  
            return;    
        }  
        System.out.println("in run() - leaving normally");  
    }  

    public static void main(String[] args) {  
        SleepInterrupt si = new SleepInterrupt();  
        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");  
    }  
} 

运行结果如下:

主线程启动新线程后,自身休眠 2 秒钟,允许新线程获得运行时间。新线程打印信息about to sleep for 20 seconds后,继而休眠 20 秒钟,大约 2 秒钟后,main 线程通知新线程中断,那么新线程的 20 秒的休眠将被打断,从而抛出 InterruptException 异常,执行跳转到 catch 块,打印出interrupted while sleeping信息,并立即从 run()方法返回,然后消亡,而不会打印出 catch 块后面的leaving normally信息。

请注意:由于不确定的线程规划,上图运行结果的后两行可能顺序相反,这取决于主线程和新线程哪个先消亡。但前两行信息的顺序必定如上图所示。

另外,如果将 catch 块中的 return 语句注释掉,则线程在抛出异常后,会继续往下执行,而不会被中断,从而会打印出leaving normally信息。

待决中断

在上面的例子中,sleep()方法的实现检查到休眠线程被中断,它会相当友好地终止线程,并抛出 InterruptedException 异常。另外一种情况,如果线程在调用 sleep()方法前被中断,那么该中断称为待决中断,它会在刚调用 sleep()方法时,立即抛出 InterruptedException 异常。

下面的代码演示了待决中断:

public class PendingInterrupt extends Object {  
    public static void main(String[] args){  
        //如果输入了参数,则在mian线程中中断当前线程(亦即main线程)  
        if( args.length > 0 ){  
            Thread.currentThread().interrupt();  
        }   
        //获取当前时间  
        long startTime = System.currentTimeMillis();  
        try{  
            Thread.sleep(2000);  
            System.out.println("was NOT interrupted");  
        }catch(InterruptedException x){  
            System.out.println("was interrupted");  
        }  
        //计算中间代码执行的时间  
        System.out.println("elapsedTime=" + ( System.currentTimeMillis() - startTime));  
    }  
}

如果 PendingInterrupt 不带任何命令行参数,那么线程不会被中断,最终输出的时间差距应该在 2000 附近(具体时间由系统决定,不精确),如果 PendingInterrupt 带有命令行参数,则调用中断当前线程的代码,但 main 线程仍然运行,最终输出的时间差距应该远小于 2000,因为线程尚未休眠,便被中断,因此,一旦调用 sleep()方法,会立即打印出 catch 块中的信息。

执行结果如下:

这种模式下,main 线程中断它自身。除了将中断标志(它是 Thread 的内部标志)设置为 true 外,没有其他任何影响。线程被中断了,但 main 线程仍然运行,main 线程继续监视实时时钟,并进入 try 块,一旦调用 sleep()方法,它就会注意到待决中断的存在,并抛出 InterruptException。于是执行跳转到 catch 块,并打印出线程被中断的信息。最后,计算并打印出时间差。

使用 isInterrupted()方法判断中断状态

可以在 Thread 对象上调用 isInterrupted()方法来检查任何线程的中断状态。这里需要注意:线程一旦被中断,isInterrupted()方法便会返回 true,而一旦 sleep()方法抛出异常,它将清空中断标志,此时isInterrupted()方法将返回 false。

下面的代码演示了 isInterrupted()方法的使用:

public class InterruptCheck extends Object{  
    public static void main(String[] args){  
        Thread t = Thread.currentThread();  
        System.out.println("Point A: t.isInterrupted()=" + t.isInterrupted());  
        //待决中断,中断自身  
        t.interrupt();  
        System.out.println("Point B: t.isInterrupted()=" + t.isInterrupted());  
        System.out.println("Point C: t.isInterrupted()=" + t.isInterrupted());  

        try{  
            Thread.sleep(2000);  
            System.out.println("was NOT interrupted");  
        }catch( InterruptedException x){  
            System.out.println("was interrupted");  
        }  
        //抛出异常后,会清除中断标志,这里会返回false  
        System.out.println("Point D: t.isInterrupted()=" + t.isInterrupted());  
    }  
}  

运行结果如下:

使用 Thread.interrupted()方法判断中断状态

可以使用 Thread.interrupted()方法来检查当前线程的中断状态(并隐式重置为 false)。又由于它是静态方法,因此不能在特定的线程上使用,而只能报告调用它的线程的中断状态,如果线程被中断,而且中断状态尚不清楚,那么,这个方法返回 true。与 isInterrupted()不同,它将自动重置中断状态为 false,第二次调用 Thread.interrupted()方法,总是返回 false,除非中断了线程。

如下代码演示了 Thread.interrupted()方法的使用:

public class InterruptReset extends Object {  
    public static void main(String[] args) {  
        System.out.println(  
            "Point X: Thread.interrupted()=" + Thread.interrupted());  
        Thread.currentThread().interrupt();  
        System.out.println(  
            "Point Y: Thread.interrupted()=" + Thread.interrupted());  
        System.out.println(  
            "Point Z: Thread.interrupted()=" + Thread.interrupted());  
    }  
}

运行结果如下:

从结果中可以看出,当前线程中断自身后,在 Y 点,中断状态为 true,并由 Thread.interrupted()自动重置为 false,那么下次调用该方法得到的结果便是 false。

补充

这里补充下 yield 和 join 方法的使用。

  • join 方法用线程对象调用,如果在一个线程 A 中调用另一个线程 B 的 join 方法,线程 A 将会等待线程 B 执行完毕后再执行。
  • yield 可以直接用 Thread 类调用,yield 让出 CPU 执行权给同等级的线程,如果没有相同级别的线程在等待 CPU 的执行权,则该线程继续执行。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值