博文一:https://www.jianshu.com/p/a8abe097d4ed
InterruptedException异常
在了解InterruptedException异常之前应该了解以下的几个关于线程的一些基础知识。而且得知道什么时候会抛InterruptedException异常
当阻塞方法收到中断请求的时候就会抛出InterruptedException异常
线程的状态
线程在一定的条件下会发生状态的改变,下面是线程的一些状态
- 初始(NEW):新建一个线程的对象,还未调用start方法
- 运行(RUNNABLE):java线程中将已经准备就绪(Ready)和正在运行中(Running)的两种状态都统称为“Runnable”。准备就绪的线程会被放在线程池中等待被调用
- 阻塞(BLOCKED):是因为某种的原因而放弃了CPU的使用权,暂时的停止了运行。直到线程进入准备就绪(Ready)状态才会有机会转到运行状态
- 等待(WAITING):该状态的线程需要等待其他线程做出一些特定的动作(通知或者是中断)
- 超时等待(TIME_WAITING):该状态和上面的等待不同,他可以在指定的时间内自行返回
- 终止(TERMINATED):线程任务执行完毕
而InterruptedException异常从字面意思上就是中断异常,那么什么是中断呢?学习中断之前我们先了解一下具体什么是阻塞
线程阻塞
线程阻塞通常是指一个线程在执行过程中暂停,以等待某个条件的触发。而什么情况才会使得线程进入阻塞的状态呢?
- 等待阻塞:运行的线程执行wait()方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池”中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify()或notifyAll()方法才能被唤醒
- 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入“锁池”中
- 其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态
线程中断
如果我们有一个运行中的软件,例如是杀毒软件正在全盘查杀病毒,此时我们不想让他杀毒,这时候点击取消,那么就是正在中断一个运行的线程。
每一个线程都有一个boolean类型的标志,此标志意思是当前的请求是否请求中断,默认为false。当一个线程A调用了线程B的interrupt方法时,那么线程B的是否请求的中断标志变为true。而线程B可以调用方法检测到此标志的变化。
- 阻塞方法:如果线程B调用了阻塞方法,如果是否请求中断标志变为了true,那么它会抛出InterruptedException异常。抛出异常的同时它会将线程B的是否请求中断标志置为false
- 非阻塞方法:可以通过线程B的isInterrupted方法进行检测是否请求中断标志为true还是false,另外还有一个静态的方法interrupted方法也可以检测标志。但是静态方法它检测完以后会自动的将是否请求中断标志位置为false。例如线程A调用了线程B的interrupt的方法,那么如果此时线程B中用静态interrupted方法进行检测标志位的变化的话,那么第一次为true,第二次就为false。下面为具体的例子:
/**
* @program: Test
* @description:
* @author: hu_pf@suixingpay.com
* @create: 2018-07-31 15:43
**/
public class InterrupTest implements Runnable{
public void run(){
try {
while (true) {
Boolean a = Thread.currentThread().isInterrupted();
System.out.println("in run() - about to sleep for 20 seconds-------" + a);
Thread.sleep(20000);
System.out.println("in run() - woke up");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();//如果不加上这一句,那么cd将会都是false,因为在捕捉到InterruptedException异常的时候就会自动的中断标志置为了false
Boolean c=Thread.interrupted();
Boolean d=Thread.interrupted();
System.out.println("c="+c);
System.out.println("d="+d);
}
}
public static void main(String[] args) {
InterrupTest si = new InterrupTest();
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");
}
}
打印的参数如下:
in run() - about to sleep for 20 seconds-------false
in main() - interrupting other thread
in main() - leaving
c=true
d=false
现在知道线程可以检测到自身的标志位的变化,但是他只是一个标志,如果线程本身不处理的话,那么程序还是会执行下去,就好比,老师在学校叮嘱要好好学习,具体什么时候,如何好好学习还是看自身。
因此interrupt() 方法并不能立即中断线程,该方法仅仅告诉线程外部已经有中断请求,至于是否中断还取决于线程自己
InterruptedException异常的处理
简单的了解了什么是阻塞和中断以后,我们就该了解碰到InterruptedException异常该如何处理了。
不要不管不顾
有时候阻塞的方法抛出InterruptedException异常并不合适,例如在Runnable中调用了可中断的方法,因为你的程序是实现了Runnable接口,然后在重写Runnable接口的run方法的时候,那么子类抛出的异常要小于等于父类的异常。而在Runnable中run方法是没有抛异常的。所以此时是不能抛出InterruptedException异常。如果此时你只是记录日志的话,那么就是一个不负责任的做法,因为在捕获InterruptedException异常的时候自动的将是否请求中断标志置为了false。至少在捕获了InterruptedException异常之后,如果你什么也不想做,那么就将标志重新置为true,以便栈中更高层的代码能知道中断,并且对中断作出响应。
捕获到InterruptedException异常后恢复中断状态
public class TaskRunner implements Runnable {
private BlockingQueue<Task> queue;
public TaskRunner(BlockingQueue<Task> queue) {
this.queue = queue;
}
public void run() {
try {
while (true) {
Task task = queue.take(10, TimeUnit.SECONDS);
task.execute();
}
}
catch (InterruptedException e) {
// Restore the interrupted status
Thread.currentThread().interrupt();
}
}
}
参考文章
作者:不学无数的程序员
链接:https://www.jianshu.com/p/a8abe097d4ed
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
前言
在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的结果。
通过上述方法的解释,我们可以得出这样的一个结论:interrupt方法本质上不会进行线程的终止操作的,它不过是改变了线程的中断状态。而改变了此状态带来的影响是,部分可中断的线程方法(比如Object.wait, Thread.sleep,Object.join)会定期执行isInterrupted方法,检测到此变化,随后会停止阻塞并抛出InterruptedException异常。
但这是否意味着随后线程的退出呢?不是的,下面是笔者对于此3个方法进行的一个简单测试demo。代码如下:
public class InterruptedException {
public static void main(String[] args) throws Exception {
System.out.println("初始中断状态:" + Thread.currentThread().isInterrupted());
Thread.currentThread().interrupt();
System.out.println("执行完interrupt方法后,中断状态:" + Thread.currentThread().isInterrupted());
System.out.println("首次调用interrupted方法返回结果:" + Thread.currentThread().interrupted());
System.out.println("此时中断状态:" + Thread.currentThread().isInterrupted());
System.out.println("第二次调用interrupted方法返回结果:" + Thread.currentThread().interrupted());
System.out.println("此时中断状态:" + Thread.currentThread().isInterrupted());
}
}
输出结果如下:
初始中断状态:false
执行完interrupt方法后,中断状态:true
首次调用interrupted方法返回结果:true
此时中断状态:false
第二次调用interrupted方法返回结果:false
此时中断状态:false
InterruptedException异常的抛出并不是意味着线程必须得终止,它只是提醒当前线程有中断操作发生了,接下来怎么处理完全取决于线程本身,一般有3种处理方式:
1.“吞并”异常,当做什么事都没发生过。
2.继续往外抛出异常。
3.其它方式处理异常。其它处理异常的方式就有很多种了,停止当前线程或者输出异常信息等等。
第三点其它中断异常处理的部分,将会是本文所要重点阐述的。这里面可有不少的讲究。
中断异常的处理
此部分的内容源自于笔者最近读过的一篇文章,IBM官网上关于JAVA理论与实践的一篇博文。里面包含了许多种情况下的处理方式
首先我们来看第一种处理方式,可以说是最简单的一种,直接往外抛出InterruptedException,代码如下:
public class TaskQueue {
private static final int MAX_TASKS = 1000;
private BlockingQueue<Task> queue
= new LinkedBlockingQueue<Task>(MAX_TASKS);
public void putTask(Task r) throws InterruptedException {
queue.put(r);
}
public Task getTask() throws InterruptedException {
return queue.take();
}
}
但是很多情况下我们会主动捕获住这个异常,然后做进一步处理,比如说再throw它一次,如下代码所示:
public class PlayerMatcher {
private PlayerSource players;
public PlayerMatcher(PlayerSource players) {
this.players = players;
}
public void matchPlayers() throws InterruptedException {
Player playerOne, playerTwo;
try {
while (true) {
playerOne = playerTwo = null;
// Wait for two players to arrive and start a new game
playerOne = players.waitForPlayer(); // could throw IE
playerTwo = players.waitForPlayer(); // could throw IE
startNewGame(playerOne, playerTwo);
}
} catch (InterruptedException e) {
// Just propagate the exception
throw e;
}
}
}
上面程序的意思是阻塞地等待2个运动员,最后再开始新的比赛,在等待运动员1和运动员2的过程中是有可能发生中断的,在这里我们的做法是进行一次简单的rethrow动作。其实这里会有2个问题,第一个如果方法在等待第二个运动员时发生中断异常了,如果就这么重抛,会导致数据的丢失,这里的丢失指的是运动员1的请求数据,因为在此处的运行过程中,运动员1的请求操作已经执行完成。这样的话,再下次重跑此任务的时候,还是会等待2次操作。所以,优化过后的代码如下:
public class PlayerMatcher {
public void matchPlayers() throws InterruptedException {
Player playerOne, playerTwo;
try {
while (true) {
playerOne = playerTwo = null;
// Wait for two players to arrive and start a new game
playerOne = players.waitForPlayer(); // could throw IE
playerTwo = players.waitForPlayer(); // could throw IE
startNewGame(playerOne, playerTwo);
}
}
catch (InterruptedException e) {
// If we got one player and were interrupted, put that player back
if (playerOne != null)
players.addFirst(playerOne);
// Then propagate the exception
throw e;
}
}
}
还有一个问题是中断状态的问题,当InterruptedException抛出的时候,线程的中断状态将会被清除,重新置为false。此时最好的一种做法是将线程状态重新置为true,这样才能最完全的保留线程当前的执行状况,而调用的方法就是前面篇幅介绍的interrupt方法。下面是一个示例代码;
public class TaskRunner implements Runnable {
private BlockingQueue<Task> queue;
public TaskRunner(BlockingQueue<Task> queue) {
this.queue = queue;
}
public void run() {
try {
while (true) {
Task task = queue.take(10, TimeUnit.SECONDS);
task.execute();
}
}
catch (InterruptedException e) {
// Restore the interrupted status
Thread.currentThread().interrupt();
}
}
}
但是有的时候我们并不总是一遇到InterruptedException,就重置状态,一种好的做法是在程序最后执行完的时候,调用一次interrupt方法即可,捕获异常时只需要做个标记即可,如下代码。
public Task getNextTask(BlockingQueue<Task> queue) {
boolean interrupted = false;
try {
while (true) {
try {
return queue.take();
} catch (InterruptedException e) {
interrupted = true;
// fall through and retry
}
}
} finally {
if (interrupted)
Thread.currentThread().interrupt();
}
}
中断状态被置为true了之后,当前线程会感知到此变化,对于后续的操作行为,完全由线程本身决定。这里会牵涉到Thread.currentThread().isInterrupted()的配合调用了。
与此做法完全相反的是,我们不能够随随便便把中断异常给“吞”了,比如说下面这种方式的代码:
public class TaskRunner implements Runnable {
private BlockingQueue<Task> queue;
public TaskRunner(BlockingQueue<Task> queue) {
this.queue = queue;
}
public void run() {
try {
while (true) {
Task task = queue.take(10, TimeUnit.SECONDS);
task.execute();
}
}
catch (InterruptedException swallowed) {
/* DON'T DO THIS - RESTORE THE INTERRUPTED STATUS INSTEAD */
}
}
}
那什么时候我们可以毫无顾虑地吞并这异常呢,这种情况也是有的,比如说当我们明确知道一个程序将要结束退出的时候。当然了,可能还有其它一些特殊场景,这个可以根据具体应用场景来定。
综述所述,线程中断异常的处理绝对不能简简单单地处理,处理不当会丢失掉许多重要的信息,不利于我们排除问题的原因。
一道阿里面试题:
public static void test() {
Thread t1 = new Thread("t1") {
@Override
public void run() {
try {
int i = 0;
while (i++ < 100000000) {
// nothing
System.out.println(i);
}
System.out.println("t1 A1");
} catch (Exception e) {
System.out.println("t1 B2");
}
}
};
t1.start();
t1.interrupt();
Thread t2 = new Thread("t2") {
@Override
public void run() {
try {
Thread.sleep(5000);
System.out.println("t2 A2");
} catch (Exception e) {
System.out.println("t2 B2");
}
}
};
t2.start();
t2.interrupt();
Thread t3 = new Thread("t3") {
@Override
public void run() {
try {
this.wait(5000);
System.out.println("t3 A3");
} catch (Exception e) {
System.out.println("t3 B3");
}
}
};
t3.start();
t3.interrupt();
Thread t4 = new Thread("t4") {
@Override
public void run() {
try {
synchronized (this) {
this.wait(5000);
}
System.out.println("t4 A4");
} catch (Exception e) {
System.