以下代码Java编程中经常会遇到,sleep 会抛出一个 InterruptedException 表示当前线程被其他线程中断。那这种处理 InterruptedException 的方式是否合理呢?

1
2
3
4
5
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}

阻塞方法

如果一个方法会抛出 InterruptedException,表明当前方法是一个阻塞方法,同时这个方法可以尝试结束阻塞并提前返回。阻塞方法的执行,依赖外部条件,例如超时、I/O完成或其他线程的某个动作(释放锁、某个状态值满足条件等)。阻塞方法事实上可以永久地等待某个条件发生,所以需要能提前结束或取消。Thread.sleep() 和 Object.wait() 支持抛出 InterruptedException ,如果抛出该异常表明当前线程被中断,可以提前结束或取消。

处理 InterruptedException

最简单的方式是将 InterruptedException 抛出,由上层调用者决定如何处理。

有的场景可能需要在抛出 InterruptedException 后做一些后续工作,这样就需要在当前线程捕获该异常,并在 catch 块中完成后续工作,最后在将异常重新抛出。

不要直接「吃掉」 InterruptedException

以下场景是我们经常遇到的:

1
2
3
4
5
6
7
8
9
final ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.submit(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
});

一个线程内部会抛出 InterruptedException ,但无法向外抛出。此时需要在捕获异常之后,设置当前线程的中断状态,即调用 Thread.currentThread().interrupt() 。这样调用栈上能够处理该异常的方法就会对该状态做出响应。

捕获到 InterruptedException 后最坏的做法就是直接「吃掉」,即便已经记录日志。标准线程池(ThreadPoolExecutor)针对线程中断有专门的处理,这样线程池中的线程就能响应「取消任务」、「线程池关闭」等中断请求。

不可中断的阻塞方法

并不是所有的阻塞方法都能够响应中断。例如input 和 output stream 会阻塞等待 I/O 完成。阻塞发生在 synchronized 代码块,也无法中断。

无法取消的任务

以下场景,不允许中断任务的执行,然而这种情况下,也需要保存任务的中断状态,这样在方法调用返回的时候能够保存中断状态,供调用方做处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public Task getNextTask(BlockingQueue<Task> queue) {
boolean interrupted = false;
try {
while (true) {
try {
return queue.take();
} catch (InterruptedException e) {
interrupted = true;
}
}
} finally {
if (interrupted)
Thread.currentThread().interrupt();
}
}

总结

利用中断机制可以实现灵活的取消策略来响应中断,线程可以决定任务取消与否,并进行相关的清理工作。如果当前方法忽略中断,那确保中断状态能重新抛出给上层调用方。