线程的中断(interrupt)机制
2017-02-19 23:50
260 查看
前言
在本博文的一篇如何正确的关闭一个线程一文中讲解了如何利用interrupt机制来中断一个线程,这篇文章当时确实花了一些精力的总结,不过都是15年末的事情了,现在是2017年2月份,经过一年的时间,决定重新写一篇完善的关于线程中断的文章。
什么时候需要关闭一个线程?
下面简单的举例情况:比如我们会启动多个线程做同一件事,比如抢12306的火车票,我们可能开启多个线程从多个渠道买火车票,只要有一个渠道买到了,我们会通知取消其他渠道。这个时候需要关闭其他线程
很多线程的运行模式是死循环,比如在生产者/消费者模式中,消费者主体就是一个死循环,它不停的从队列中接受任务,执行任务,在停止程序时,我们需要一种”优雅”的方法以关闭该线程
在一些场景中,比如从第三方服务器查询一个结果,我们希望在限定的时间内得到结果,如果得不到,我们会希望取消该任务。
总之,很多情况下我们都有关闭一个线程的需求,那么如何正确的关闭一个线程就是我们要研究的事情,这个事情在上一篇文章中已经讨论过了,这里不在赘述。
废弃的API
Thread.STOP()之类的api会造成一些不可预知的bug,所以很早便
Deprecated了,真要纠结为什么请看这边文章为何不赞成使用
Thread.stop、Thread.suspend 和 Thread.resume?
线程中断API
Thread类定义了如下关于中断的方法:API | 作用 |
---|---|
public static boolean interrupted | 就是返回对应线程的中断标志位是否为true返回当前线程的中断标志位是否为true,但它还有一个重要的副作用,就是清空中断标志位,也就是说,连续两次调用interrupted(),第一次返回的结果为true,第二次一般就是false (除非同时又发生了一次中断)。 |
public boolean isInterrupted() | 就是返回对应线程的中断标志位是否为true |
public void interrupt() | 表示中断对应的线程 |
线程对中断的反应
RUNNABLE:线程在运行或具备运行条件只是在等待操作系统调度WAITING/TIMED_WAITING:线程在等待某个条件或超时
BLOCKED:线程在等待锁,试图进入同步块
NEW/TERMINATED:线程还未启动或已结束
RUNNABLE状态
如果线程在运行中,interrupt()只是会设置线程的中断标志位,没有任何其它作用。线程应该在运行过程中合适的位置检查中断标志位,比如说,如果主体代码是一个循环,可以在循环开始处进行检查,如下所示:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public class InterruptRunnableDemo extends Thread { @Override public void run() { while (!Thread.currentThread().isInterrupted()) { // ... 单次循环代码 } System.out.println("done "); } public static void main(String[] args) throws InterruptedException { Thread thread = new InterruptRunnableDemo(); thread.start(); Thread.sleep(1000); thread.interrupt(); } } |
WAITING/TIMED_WAITING
线程执行如下方法会进入WAITING状态:1 2 | public final void join() throws InterruptedException public final void wait() throws InterruptedException |
1 23 | public final native void wait(long timeout) throws InterruptedException; public static native void sleep(long millis) throws InterruptedException; public final synchronized void join(long millis) throws InterruptedException |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 1516 | Thread t = new Thread (){ @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { //exception被捕获,但是为输出为false 因为标志位会被清空 System.out.println(isInterrupted()); } } }; t.start(); try { Thread.sleep(100); } catch (InterruptedException e) { } t.interrupt();//置为true |
捕获到InterruptedException,通常表示希望结束该线程,线程大概有两种处理方式:
向上传递该异常,这使得该方法也变成了一个可中断的方法,需要调用者进行处理
有些情况,不能向上传递异常,比如Thread的run方法,它的声明是固定的,不能抛出任何受检异常,这时,应该捕获异常,进行合适的清理操作,清理后,一般应该调用Thread的interrupt方法设置中断标志位,使得其他代码有办法知道它发生了中断
第一种方式的示例代码如下:
1 23 | //抛出中断异常,由调用者捕获 public void interruptibleMethod() throws InterruptedException{ // ... 包含wait, join 或 sleep 方法 Thread.sleep(1000); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 1516 | public class InterruptWaitingDemo extends Thread { @Override public void run() { while (!Thread.currentThread().isInterrupted()) { try { // 模拟任务代码 Thread.sleep(2000); } catch (InterruptedException e) { // ... 清理操作 System.out.println(isInterrupted());//false // 重设中断标志位为true Thread.currentThread().interrupt(); } } System.out.println(isInterrupted());//true } public static void main(String[] args) { InterruptWaitingDemo thread = new InterruptWaitingDemo(); thread.start(); try { Thread.sleep(100); } catch (InterruptedException e) { } thread.interrupt(); } } |
BLOCKED
如果线程在等待锁,对线程对象调用interrupt()只是会设置线程的中断标志位,线程依然会处于BLOCKED状态,也就是说,interrupt()并不能使一个在等待锁的线程真正”中断”。我们看段代码:1 2 3 4 5 6 7 8 9 10 11 12 13 14 1516 | public class InterruptWaitingDemo extends Thread { @Override public void run() { while (!Thread.currentThread().isInterrupted()) { try { // 模拟任务代码 Thread.sleep(2000); } catch (InterruptedException e) { // ... 清理操作 // 重设中断标志位 Thread.currentThread().interrupt(); } } System.out.println(isInterrupted()); } public static void main(String[] args) { InterruptWaitingDemo thread = new InterruptWaitingDemo(); thread.start(); try { Thread.sleep(100); } catch (InterruptedException e) { } thread.interrupt(); } } |
如果线程在等待锁,对线程对象调用interrupt()只是会设置线程的中断标志位,线程依然会处于BLOCKED状态,也就是说,interrupt()并不能使一个在等待锁的线程真正”中断”。我们看段代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 1516 | public class InterruptSynchronizedDemo { private static Object lock = new Object();//monitor private static class A extends Thread { @Override public void run() { //等待lock锁 synchronized (lock) { //等待标志位被置为true while (!Thread.currentThread().isInterrupted()) { } } System.out.println("exit"); } } public static void test() throws InterruptedException { synchronized (lock) {//获取锁 A a = new A(); a.start(); Thread.sleep(1000); //a在等待lock锁,interrupt 无法中断 a.interrupt(); //a线程加入当前线程,等待执行完毕 a.join(); } } public static void main(String[] args) throws InterruptedException { test(); } } |
我们稍微修改下代码,去掉test方法中的最后一行a.join,即变为:
1 23 | public static void test() throws InterruptedException { synchronized (lock) { A a = new A(); a.start(); Thread.sleep(1000); a.interrupt(); } //lock锁释放后 A线程重队列中出来 } |
在使用synchronized关键字获取锁的过程中不响应中断请求,这是synchronized的局限性。如果这对程序是一个问题,应该使用显式锁,java中的Lock接口,它支持以响应中断的方式获取锁。对于Lock.lock(),可以改用Lock.lockInterruptibly(),可被中断的加锁操作,它可以抛出中断异常。等同于等待时间无限长的Lock.tryLock(long time, TimeUnit unit)。
NEW/TERMINATE
如果线程尚未启动(NEW),或者已经结束(TERMINATED),则调用interrupt()对它没有任何效果,中断标志位也不会被设置。比如说,以下代码的输出都是false。1 2 3 4 5 6 7 8 9 10 11 12 13 14 1516 | public class InterruptNotAliveDemo { private static class A extends Thread { @Override public void run() { } } public static void test() throws InterruptedException { A a = new A(); a.interrupt(); System.out.println(a.isInterrupted()); a.start(); Thread.sleep(100); a.interrupt(); System.out.println(a.isInterrupted()); } public static void main(String[] args) throws InterruptedException { test(); } } |
IO操作
如果线程在等待IO操作,尤其是网络IO,则会有一些特殊的处理,我们没有介绍过网络,这里只是简单介绍下。实现此InterruptibleChannel接口的通道是可中断的:如果某个线程在可中断通道上因调用某个阻塞的 I/O 操作(常见的操作一般有这些:serverSocketChannel. accept()、socketChannel.connect、socketChannel.open、socketChannel.read、socketChannel.write、fileChannel.read、fileChannel.write)而进入阻塞状态,而另一个线程又调用了该阻塞线程的 interrupt
方法,这将导致该通道被关闭,并且已阻塞线程接将会收到ClosedByInterruptException,并且设置已阻塞线程的中断状态。另外,如果已设置某个线程的中断状态并且它在通道上调用某个阻塞的 I/O 操作,则该通道将关闭并且该线程立即接收到 ClosedByInterruptException;并仍然设置其中断状态。
如果线程阻塞于Selector调用,则线程的中断标志位会被设置,同时,阻塞的调用会立即返回。
我们重点介绍另一种情况,InputStream的read调用,该操作是不可中断的,如果流中没有数据,read会阻塞 (但线程状态依然是RUNNABLE),且不响应interrupt(),与synchronized类似,调用interrupt()只会设置线程的中断标志,而不会真正”中断”它,我们看段代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 1516 | public class InterruptReadDemo { private static class A extends Thread { @Override public void run() { while(!Thread.currentThread().isInterrupted()){ try { System.out.println(System.in.read())//wait input } catch (IOException e) { e.printStackTrace(); } } System.out.println("exit"); } } public static void main(String[] args) throws InterruptedException { A t = new A(); t.start(); Thread.sleep(100); t.interrupt(); } } |
不过,有一个办法可以中断read()调用,那就是调用流的close方法,我们将代码改为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 1516 | public class InterruptReadDemo { private static class A extends Thread { @Override public void run() { while (!Thread.currentThread().isInterrupted()) { try { System.out.println(System.in.read()); } catch (IOException e) { e.printStackTrace(); } } System.out.println("exit"); } public void cancel() { try { System.in.close(); } catch (IOException e) { } interrupt(); } } public static void main(String[] args) throws InterruptedException { A t = new A(); t.start(); Thread.sleep(100); t.cancel(); } } |
1 2 | -1 exit |
如何正确地取消/关闭线程
以上,我们可以看出,interrupt方法不一定会真正”中断”线程,它只是一种协作机制,如果不明白线程在做什么,不应该贸然的调用线程的interrupt方法,以为这样就能取消线程。对于以线程提供服务的程序模块而言,它应该封装取消/关闭操作,提供单独的取消/关闭方法给调用者,类似于InterruptReadDemo中演示的cancel方法,外部调用者应该调用这些方法而不是直接调用interrupt。
Java并发库的一些代码就提供了单独的取消/关闭方法,比如说,Future接口提供了如下方法以取消任务:boolean cancel(boolean mayInterruptIfRunning);
再比如,ExecutorService提供了如下两个关闭方法:
1 2 | void shutdown(); List<Runnable> shutdownNow(); |
相关文章推荐
- 线程中断 interrupt 机制
- 线程的interrupt中断和取消机制
- java 多线程5: java 终止线程及中断机制 (stop()、interrupt() 、interrupted()、isInterrupted())
- 线程中断 thread.interrupt()的用法
- 用interrupt()中断Java线程
- Java多线程-通过线程的中断来深入学习interrupt方法,Volatile关键字
- Java线程_线程中断(interrupt)相关
- Thread的中断机制(interrupt)
- 第二篇 多线程的使用——中断线程详解(Interrupt)
- 关于线程的中断机制
- Java并发编程(二)线程任务的中断(interrupt)
- 线程中断之interrupt和stop方法
- 同步——synchronized机制、Lock和Conditon机制和关于线程中断
- 线程中断机制及响应
- Java线程中断interrupt详解
- Thread的中断机制(interrupt)
- 多线程的使用——中断线程详解(Interrupt)
- Java多线程之中断线程(Interrupt)的使用详解
- 线程中断方法interrupt() 与 cancel()
- 线程间通信 等待唤醒机制 wait notify notifyAll lock Condition唤醒 停止线程interrupt 守护线程setDaemon join yield