在java语言的开发工作中,我们经常会碰到这样一类异常--InterruptedException(中断异常)。在绝大多数时候,我们的处理方式无非是catch住它,然后再输出异常信息,更或者是干脆直接忽略它了。那么这是否是一种正确的处理方式呢,要想搞清楚这件事,我们又必须要了解什么是InterruptedException,什么情况下会导致此异常的发生呢?本文笔者来简单讲述讲述这方面的内容,了解中断中断异常方面的知识将有助于我们在分布式的程序中处理这样的异常。
什么是中断异常
现在一个首要的问题来了,什么是中断异常,InterruptedException到底意味着什么意思呢?下面是笔者通过阅读IBM官网上面对于此的定义:
When a method throws InterruptedException, it is telling you several things in addition to the fact that it can throw a particular checked exception. It is telling you that it is a blocking method and that it will make an attempt to unblock and return early
大致意思如下:InterruptedException实质上是一个检测异常,它表明有一个阻塞的方法被中断了,它尝试进行解除阻塞操作并返回地更早一些。中断阻塞方法的操作线程并不是自身线程干的,而是其它线程。而中断操作发生之后,随后会抛出一个InterruptedException,伴随着这个异常抛出的同时,当前线程的中断状态重新被置为false。这时,我们谈到了线程的中断状态,可能有些读者会有点晕了,下面我们来理理这段关系。
1.public static boolean interrupted(); // 检测当前线程是否已经中断,此方法会清除中断状态,也就是说,假设当前线程中断状态为true,第一次调此方法,将返回true,表明的确已经中断了,但是第二次调用后,将会返回false,因为第一次调用的操作已将中断状态重新置为false了。
2.public boolean isInterrupted() ; // 检测调用该方法的对象所表示的线程是否已经中断,此方法与上一方法的区别在于此方法不会清除中断状态。
3.public void interrupt(); //将线程中断状态设置为true,表明此线程目前是中断状态。此时如果调用isInterrupted方法,将会得到true的结果。
1和2一个是静态方法一个是实例方法,它们分别测试的是:当前线程的中断状态和实例对象所表示的线程的中断状态。
通过上述方法的解释,我们可以得出这样的一个结论:interrupt方法本质上不会进行线程的终止操作的,它不过是改变了线程的中断状态。而改变了此状态带来的影响是,部分可中断阻塞线程方法(比如Object.wait, Thread.sleep,Thread.join)会定期执行isInterrupted方法,检测到此变化,随后会停止阻塞并抛出InterruptedException异常。
1 public class Test1
2 {
3 public static void main(String[] args) throws Exception
4 {
5 System.out.println("初始中断状态:" + Thread.currentThread().isInterrupted());
6 Thread.currentThread().interrupt();
7 System.out.println("执行完interrupt方法后,中断状态:" + Thread.currentThread().isInterrupted());
8 System.out.println("首次调用interrupted方法返回结果:" + Thread.currentThread().interrupted());
9 System.out.println("此时中断状态:" + Thread.currentThread().isInterrupted());
10 System.out.println("第二次调用interrupted方法返回结果:" + Thread.currentThread().interrupted());
11 System.out.println("此时中断状态:" + Thread.currentThread().isInterrupted());
12 }
13 }
打印结果
但抛出异常这是否意味着随后线程的退出呢?不是的,InterruptedException异常的抛出并不是意味着线程必须得终止,它只是提醒当前线程有中断操作发生了,接下来怎么处理完全取决于线程本身,一般有3种处理方式。
看一个纠结的例子
1 public class Test
2 {
3 public static void main(String[] args)
4 {
5 try
6 {
7 Thread thread = new Thread()
8 {
9 public void run()
10 {
11
12 }
13 };
14 thread.start();
15 Thread.sleep(1000);
16 thread.interrupt();
17 System.out.println(thread.isInterrupted());
18 }
19 catch(Exception e)
20 {
21 e.printStackTrace();
22 }
23 }
24 }
这里当运行上面一段代码时,打印的是false,当我们去掉 Thread.sleep(1000);时,有大部分时候可能打印 true,也可能打印 false。
按照我们上面所讲,调用 interrupt() 应该会中断设置中断状态,这时我们调用 isInterrupted() 应该返回 true。
但显然不适合这里。所以,我们来分析一下原因。其实原因就两句话:
不加sleep时,调用thread.interrupt()时可能线程还活着
加上sleep时,调用thread.interrupt()时线程已经结束了,而对于方法interrupt()不会修改一个已经结束的线程的中断标志位。
其实我们可以加上在16、17行代码之间加上判断状态的语句
1 System.out.println(thread.getName() + " " + thread.getState());
你就会发现,当状态为 RUNNABLE 时,打印 true,当显示 TERMINATED 时,打印 false。
再来几个复杂的例子
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 }
结果为
1 t1 (NEW) is new.
2 t1 (RUNNABLE) is started.
3 t1 (RUNNABLE) loop 1
4 t1 (RUNNABLE) loop 2
5 t1 (TIMED_WAITING) is interrupted.
6 t1 (RUNNABLE) catch InterruptedException.
7 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 @Override
2 public void run() {
3 int i=0;
4 while (!isInterrupted()) {
5 try {
6 Thread.sleep(100); // 休眠100ms
7 } catch (InterruptedException ie) {
8 System.out.println(Thread.currentThread().getName() +" ("+this.getState()+") catch InterruptedException.");
9 }
10 i++;
11 System.out.println(Thread.currentThread().getName()+" ("+this.getState()+") loop " + i);
12 }
13 }
结果为
1 t1 (NEW) is new.
2 t1 (RUNNABLE) is started.
3 t1 (RUNNABLE) loop 1
4 t1 (RUNNABLE) loop 2
5 t1 (TIMED_WAITING) is interrupted.
6 t1 (RUNNABLE) catch InterruptedException.
7 t1 (RUNNABLE) loop 3
8 t1 (RUNNABLE) loop 4
9 t1 (RUNNABLE) loop 5
10 t1 (TIMED_WAITING) is interrupted now.
11 t1 (RUNNABLE) loop 6
12 t1 (RUNNABLE) loop 7
13 t1 (RUNNABLE) loop 8
14 t1 (RUNNABLE) loop 9
15 ...
结果说明:
程序进入了死循环!
为什么会这样呢?这是因为,t1在“等待(阻塞)状态”时,被interrupt()中断;此时会改变中断标记,当t1线程再次获得cpu资源执行sleep()时,检测到中断标记改变会抛出InterruptedException异常[该异常在while循环体内被捕获],并且随着异常的抛出会清除中断标记[即isInterrupted()会返回false]。因此,t1理所当然的会进入死循环了。
解决该问题,需要我们在捕获异常时,额外的进行退出while循环的处理。例如,在MyThread的catch(InterruptedException)中添加break 或 return就能解决该问题。