什么是线程中断
Java中断机制是一种协作机制,也就是说通过中断并不能直接终止另一个线程,而需要被中断的线程自己处理中断.
当一个线程运行时,另一个线程可以调用对应的 Thread 对象的 interrupt()方法来中断它,该方法只是在目标线程中设置一个标志,表示它已经被中断,并立即返回。这里需要注意的是,如果只是单纯的调用 interrupt()方法,线程并没有实际被中断,会继续往下执行。
线程自身也可以中断自身。
感性认识中断
示例代码
/**
* Created by haicheng.lhc on 07/07/2017.
*
* @author haicheng.lhc
* @date 2017/07/07
*/
public class TestInterrupt 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) {
TestInterrupt si = new TestInterrupt();
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 异常。
/**
* Created by haicheng.lhc on 07/07/2017.
*
* @author haicheng.lhc
* @date 2017/07/07
*/
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));
}
}
结果
中断的原理
Java中断机制是一种协作机制,也就是说通过中断并不能直接终止另一个线程,而需要被中断的线程自己处理中断
Java中断模型也是这么简单,每个线程对象里都有一个boolean类型的标识(不一定就要是Thread类的字段,实际上也的确不是,这几个方法最终都是通过native方法来完成的),代表着是否有中断请求(该请求可以来自所有线程,包括被中断的线程本身)。例如,当线程t1想中断线程t2,只需要在线程t1中将线程t2对象的中断标识置为true,然后线程2可以选择在合适的时候处理该中断请求,甚至可以不理会该请求,就像这个线程没有被中断一样。
- java.lang.Thread类提供了几个方法来操作这个中断状态,这些方法包括:
方法名 | 描述 |
---|---|
public static boolean interrupted | 测试当前线程是否已经中断。线程的中断状态 由该方法清除。换句话说,如果连续两次调用该方法,则第二次调用将返回 false(在第一次调用已清除了其中断状态之后,且第二次调用检验完中断状态前,当前线程再次中断的情况除外)。 |
public boolean isInterrupted() | 测试线程是否已经中断。线程的中断状态不受该方法的影响。 |
public void interrupt() | 中断线程。 |
- interrupt方法是唯一能将中断状态设置为true的方法。
- 静态方法interrupted会将当前线程的中断状态清除,但这个方法的命名极不直观,很容易造成误解
中断的处理
既然Java中断机制只是设置被中断线程的中断状态,那么被中断线程该做些什么?
1、处理时机
显然,作为一种协作机制,不会强求被中断线程一定要在某个点进行处理。实际上,被中断线程只需在合适的时候处理即可,如果没有合适的时间点,甚至可以不处理,这时候在任务处理层面,就跟没有调用中断方法一样。“合适的时候”与线程正在处理的业务逻辑紧密相关,例如,每次迭代的时候,进入一个可能阻塞且无法中断的方法之前等,但多半不会出现在某个临界区更新另一个对象状态的时候,因为这可能会导致对象处于不一致状态。
处理时机决定着程序的效率与中断响应的灵敏性。频繁的检查中断状态可能会使程序执行效率下降,相反,检查的较少可能使中断请求得不到及时响应。如果发出中断请求之后,被中断的线程继续执行一段时间不会给系统带来灾难,那么就可以将中断处理放到方便检查中断,同时又能从一定程度上保证响应灵敏度的地方。当程序的性能指标比较关键时,可能需要建立一个测试模型来分析最佳的中断检测点,以平衡性能和响应灵敏性。
2、处理方式
(1)中断状态的管理
一般说来,当可能阻塞的方法声明中有抛出InterruptedException则暗示该方法是可中断的,如BlockingQueue#put、BlockingQueue#take、Object#wait、Thread#sleep等,如果程序捕获到这些可中断的阻塞方法抛出的InterruptedException或检测到中断后,这些中断信息该如何处理?一般有以下两个通用原则:
如果遇到的是可中断的阻塞方法抛出InterruptedException,可以继续向方法调用栈的上层抛出该异常,如果是检测到中断,则可清除中断状态并抛出InterruptedException,使当前方法也成为一个可中断的方法。
若有时候不太方便在方法上抛出InterruptedException,比如要实现的某个接口中的方法签名上没有throws InterruptedException,这时就可以捕获可中断方法的InterruptedException并通过Thread.currentThread.interrupt()来重新设置中断状态。如果是检测并清除了中断状态,亦是如此。
一般的代码中,尤其是作为一个基础类库时,绝不应当吞掉中断,即捕获到InterruptedException后在catch里什么也不做,清除中断状态后又不重设中断状态也不抛出InterruptedException等。因为吞掉中断状态会导致方法调用栈的上层得不到这些信息。
(2)中断的响应
程序里发现中断后该怎么响应?这就得视实际情况而定了。有些程序可能一检测到中断就立马将线程终止,有些可能是退出当前执行的任务,继续执行下一个任务……作为一种协作机制,这要与中断方协商好,当调用interrupt会发生些什么都是事先知道的,如做一些事务回滚操作,一些清理工作,一些补偿操作等。若不确定调用某个线程的interrupt后该线程会做出什么样的响应,那就不应当中断该线程。