java多线程之中断(interrupt)问题
2017-07-21 09:23
435 查看
摘要
在java中,想要让一个线程停下来,有三种办法:(1)采用退出标志,使得run方法执行完之后线程自然终止。
(2)使用stop强行终止线程,但该方法由于安全问题已经被deprecated。
(3)使用中断机制。
引入
第一种方法没特别之处,无非是在覆盖Runnable接口之时对run方法中添加状态标识逻辑。比如:public class MyThread extends Thread { private boolean running; @Override public run(){ if(running){ //....business methods } } }
下面详细讨论中断机制。
这里的中断是软中断,即在某个线程中可以将目标线程的中断标志位置位,然后在目标线程进行中断检查的时候来判断自己应当如何响应中断。
特别的,可以以此来停止目标线程。中断的局限性,无法对出于死锁状态的线程起作用。
简单原理:
java.lang.Thread类中提供了一个方法来将线程的中断标志位置位:
Thread.interrupt()方法,其函数申明为:
public void interrupt();该方法会将线程的中断标志位置位。
java.lang.Thread类还两个方法来检查中断标志:
public static boolean interrupted() { return currentThread().isInterrupted(true); } public boolean isInterrupted() { return isInterrupted(false); }
这两个方法分别调用同一个原生方法来检查中断状态并有所不同。
一方面
interrupted()是静态方法,可以使用
Thread.interrupted()的方法来调用。
而
isInterrupted()则需要一个实例对象作为隐式参数,使用
threadInstance.isInterrupted()来调用。
另一方面,interrupted在调用后会立即将线程中断标志位复位,而isInterrupted则不会这么做。
非阻塞线程的中断问题:
调用Thread.interrupt将目标线程中断标志位置位,然后在目标线程中检查中断标志位。如果检查到中断之后使用throw new InterruptedException()抛出一个异常,然后使用catch语句来捕获这个异常,则可以在这里完成一些中断逻辑。
特别的,可以使用这种方法退出线程。下面是这种退出方式的举例。
package com.kingbaron.jinterrupt; /** * Created by kingbaron on 2016/3/17. */ public class UnblockingInterrupt extends Thread{ @Override public void run(){ super.run(); try{ for(int i=0;i<10000;i++) { if(Thread.interrupted()) //if(this.isInterrupted()) { System.out.println("I'm interrupted and I'm going out"); throw new InterruptedException(); } System.out.println("i="+(i+1)); } System.out.println("I'm below the for clauses and I have no chance to run here"); }catch(InterruptedException e){ System.out.println("I'm in the catch clauses of the method UnblockingInterrupt.run()"); e.printStackTrace(); } } }
package com.kingbaron.jinterrupt; /** * Created by kingbaron on 2016/3/17. */ public class TestUnblockingInterrupt { public static void main(String[] args){ try{ UnblockingInterrupt thread=new UnblockingInterrupt(); thread.start(); Thread.sleep(2000); thread.interrupt(); }catch(InterruptedException e) { System.out.println("main catch"); e.printStackTrace(); } } }
输出结果(关键部分)为:
i=277919 i=277920 I'm interrupted and I'm going out I'm in the catch clauses of the method UnblockingInterrupt.run java.lang.InterruptedException at com.kingbaron.jinterrupt.UnblockingInterrupt.run(UnblockingInterrupt.java:16)
可以看到,当线程thread的中断标志位被main线程中使用thread.interrupt置位后,在执行thread线程的for循环的if条件检查时,
Thread.interrupted()返回true随后在其中抛出一个InterruptedException异常,再使用catch子句捕获该异常,由于run方法中在catch子句之后再无语句,
故线程从run方法返回,thread线程就此终止。
阻塞线程的中断问题。
首先,一个线程出于阻塞中断状态(实际上还包括等待状态)的原因,常见的有线程调用了thread.sleep、thread.join、thread.wait、1.5中的condition.await、以及可中断的通道上的 I/O 操作方法等。
如果一个线程处于阻塞状态,若将其中断标志位置为,则会在产生阻塞的语句出抛出一个InterruptedException异常,
并且在抛出异常后立即将线程的中断标示位复位,即重新设置为false。
抛出异常是为了线程从阻塞状态醒过来,并在结束线程前让程序员有足够的时间来处理中断请求。
换言之,中断将一个处于阻塞状态的线程的阻塞状态解除。
注意,synchronized在获锁的过程中是不能被中断的,意思是说如果产生了死锁,则不可能被中断(请参考后面的测试例子)。
与synchronized功能相似的reentrantLock.lock()方法也是一样,它也不可中断的。
即如果发
4000
生死锁,那么reentrantLock.lock()方法无法终止,如果调用时被阻塞,则它一直阻塞到它获取到锁为止。
但是如果调用带超时的tryLock方法reentrantLock.tryLock(long timeout, TimeUnit unit),那么如果线程在等待时被中断,将抛出一个InterruptedException异常,这是一个非常有用的特性,因为它允许程序打破死锁。
你也可以调用reentrantLock.lockInterruptibly()方法,它就相当于一个超时设为无限的tryLock方法。
没有任何语言方面的需求一个被中断的线程应该终止。中断一个线程只是为了引起该线程的注意,被中断线程可以决定如何应对中断。
某些线程非常重要,以至于它们应该不理会中断,而是在处理完抛出的异常之后继续执行,但是更普遍的情况是,一个线程将把中断看作一个终止请求。
这种线程的run方法遵循如下形式:
public void run() { try { ... /* * 不管循环里是否调用过线程阻塞的方法如sleep、join、wait,这里还是需要加上 * !Thread.currentThread().isInterrupted()条件,虽然抛出异常后退出了循环,显 * 得用阻塞的情况下是多余的,但如果调用了阻塞方法但没有阻塞时,这样会更安全、更及时。 */ while (!Thread.currentThread().isInterrupted()&& more work to do) { do more work } } catch (InterruptedException e) { //线程在wait或sleep期间被中断了 } finally { //线程结束前做一些清理工作 } }
在这里,应当注意一个原则:尽量不要在底层代码中捕获InterruptedException原则。因为该异常一旦被捕获,线程的中断标志位就会被置位。
对于底层代码而言,如果代码会抛出不知道应当如何应对的InterruptException异常,可以有下面两个选择。
(1)捕获到InterruptedException异常后将中断标志位置位,让外层代码根据检查中断标志位来判断是否中断。
public subTask(){ try{ //...the works may throw a InterruptedException }catch(InterruptedException e){ Thread.currentThread.interrupt(); } }
(2)更推荐的方法是,底层代码不捕获InterruptedException,直接将其抛给外层代码去解决
public subTask() throws InterruptedException { //... }
中断失效的情况之临界区:
进入临界区的代码是不允许中断的。这一点很好理解,临界区是并行问题为了保护临界资源的互斥访问而特地加锁的,一旦可以中断,那么锁的存在也就没有任何意义了。特别的,形成了死锁的线程会分别处于阻塞状态,但是它们都无法被中断。
常见的有进入synchronized块的代码以及Lock.lock()之后没能得到锁而处于阻塞的状态。通常可以使用Lock.lockInterruptibly()来代替Lock.lock(),
因为Lock.lockInterruptibly()可以接受中断,这个中断指的是,既然没能得到锁进入临界区,与其阻塞不如做些别的什么事。
一旦进入临界区,任何方法都不得中断。
中断失效的情况之不可中断I/O:
自java 1.4之后对大量数据的I/O常用通道(channels)机制,它是可以被中断的I/O,也就是说如果这种I/O处于阻塞状态,可以使用中断来解除阻塞。但是存在着一些不可中断的操作,比如ServerSocket.accept(),inputSteam.read()等调用interrupt()对于这几个问题无效,因为它们都不抛出中断异常。如果拿不到资源,它们会无限期阻塞下去。
对于inputStream等资源,有些(实现了interruptibleChannel接口)可以通过close()方法将资源关闭,对应的阻塞也会被放开。
对于处理大型I/O时,推荐使用Channels。
相关文章推荐
- java 多线程基础之二:线程的中断(interrupt)
- java 线程退出问题(interrupt()与中断标志)
- Java之多线程interrupt中断线程的三种方法
- java 多线程 中断线程interrupt 研究
- Java基础 多线程 解决安全问题 等待唤醒机制 Lock Condition interrupt join setPriority yield
- Java 多线程--interrupt()中断
- Java多线程-通过线程的中断来深入学习interrupt方法,Volatile关键字
- Java多线程(二)——线程中断interrupt
- java多线程中断学习
- 编写多线程Java应用程序常见问题
- 用JAVA中的多线程示例银行取款问题
- Java多线程中的两个问题
- 用JAVA的多线程实现银行取款的问题
- java高级多线程编程--关于线程的停止问题
- 编写多线程的Java应用程序-如何避免当前编程中最常见的问题
- java多线程之NIO的中断
- JAVA的for each在多线程环境下问题
- Java多线程中的两个问题
- 由生产者/消费者问题看JAVA多线程
- 编写多线程Java应用程序常见问题