Java Thread.interrupt interrupted
Java Thread.interrupt
@(Base)[JDK, 线程, interrupt]
下面这个场景你可能很熟悉,我们调用Thread.sleep(),condition.await(),但是IDE提示我们有未捕获的InterruptedException。什么是InterruptedException呢?我们又应该怎么处理呢?
大部分人的回答是,吞掉这个异常就好啦。但是其实,这个异常往往带有重要的信息,可以让我们具备关闭应用时执行当前代码回收的能力。
Blocking Method
如果一个方法抛出InterruptedException(或者类似的),那么说明这个方法是一个阻塞方法。(非阻塞的方法需要你自己判断,阻塞方法只有通过异常才能反馈出这个东西)
当我们直接调用一个Unschedule的线程的interrupt方法的时候,会立即使得其变成schedule(这一点非常重要,由JVM保证), 并且interrupted状态位为true。
通常low-level method,像sleep和await这些方法就会在方法内部处理这个标志位。例如await就会在醒来之后检查是否有中断。所以在Sync内部通常在唤醒之后都会检查中断标志位。
看下面一段代码:
public static void main(String[] args) {
Thread a = new Thread(new Runnable() {
@Override
public void run() {
long start = System.currentTimeMillis();
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// what to do ?
}
System.out.println(System.currentTimeMillis() - start);
}
});
a.start();
// 加上这句话,执行时间是0,没有这句话执行时间是10,你感受下
a.interrupt();
}
所以,当我们直接Interrupt一个线程的时候,他会立即变成可调度的状态,也就是会里面从阻塞函数中返回。这个时候我们拿到InterruptedException(或者在更底层看来只是一个线程中断标志位)的时候应该怎么做呢?
在low-level的层面来说,只有中断标志位,这一个概念,并没有interruptException,只是jdk的框架代码中,为了强制让客户端处理这种异常,所以在同步器、线程等阻塞方法中唤醒后自动检测了中断标志位,如果符合条件,则直接抛出受检异常。
How to Deal
Propagating InterruptedException to callers by not catching it
当你的方法调用一个blocking方法的时候,说明你这个方法也是一个blocking方法(大多数情况下)。这个时候你就需要对interruptException有一定的处理策略,通常情况下最简单的策略是把他抛出去。参考下面的代码:
参考blockingQueue的写法,底层使用condition对象,当await唤醒的时候有interruptException的时候,直接抛出,便于上层处理。换句话说,你的代码这个层面没有处理的必要和意义。
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();
}
}
Do some clean up before thrown out
有时候,在当前的代码层级上,抛出interruptException需要清理当前的类,清理完成后再把异常抛出去。下面的代码,表现的就是一个游戏匹配器的功能,首先等待玩家1,玩家2都到达之后开始游戏。如果当前玩家1到达了,线程接受到interrupt请求,那么释放玩家1,这样就不会有玩家丢失。
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) {
// If we got one player and were interrupted, put that player back
if (playerOne != null)
players.addFirst(playerOne);
// Then propagate the exception
throw e;
}
}
}
Resorting Status
如果已经到了抛不出去的地步了,比如在Runnable中。当一个blocking-method抛出一个interruptException的时候,当前线程的中断标志位实际是已经被清除了的,如果我们这个时候不能再次抛出interruptException,我们就无法向上层表达中断的意义。这个时候只有重置中断状态。但是,这里面还是有很多技巧...不要瞎搞:
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();
}
}
}
注意上面代码,catch异常的位置,在看下面一段代码
public class TaskRunner implements Runnable {
private BlockingQueue<Task> queue;
public TaskRunner(BlockingQueue<Task> queue) {
this.queue = queue;
}
public void run() {
while (true) {
try {
Task task = queue.take(10, TimeUnit.SECONDS);
task.execute();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
这段代码就会造成无限循环,catch住之后,设置中断标志,然后loop,
take()函数立即抛出InterruptException。你感受下。
千万不要直接吞掉
当你不能抛出InterruptedException,不论你决定是否响应interrupt request,这个时候你都必须重置当前线程的interrupt标志位,因为interrupt标志位不是给你一个人看的,还有很多逻辑相应这个状态。标准的线程池(ThreadPoolExecutor)的Worker对象(内部类)其实也会对interrupt标识位响应,所以向一个task发出中断信号又两个作用,1是取消这个任务,2是告诉执行的Thread线程池正在关闭。如果一个task吞掉了中断请求,worker thread就不能响应中断请求,这可能导致application一直不能shutdown.
万不要直接吞掉
// Don't do this
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 */
}
}
}
Implementing cancelable tasks
从来没有任何文档给出interruption明确的语义,但是其实在大型程序中,中断可能只有一个语义:取消,因为别的语义实在是难以维持。举个例子,一个用户可以用通过GUI程序,或者通过一些网络机制例如JMX或者WebService来发出一个关闭请求。也可能是一段程序逻辑,再举个简单的例子,一个爬虫程序如果检测到磁盘满了,可能就会自行发出中断(取消)请求。或者一个并行算法可能会打开多个线程来搜索什么东西,当某个框架搜索到结果之后,就会发出中断(取消)请求。
一个task is cancelable并不意味着他必须立刻响应中断请求。如果一个task在loop中执行,一个典型的写法是在每次Loop中都检查中断标志位。可能循环时间会对响应时间造成一定的delay。你可以通过一些写法来提高中断的响应速度,例如blocking method里面往往第一行都是检查中断标志位。
Interrupts can be swallowed if you know the thread is about to exit 唯一可以吞掉interruptException的场景是,你明确知道线程就要退出了。这个场景往往出现在,调用中断方法是在你的类的内部,例如下面一段代码,而不是被某种框架中断。
public class PrimeProducer extends Thread {
private final BlockingQueue<BigInteger> queue;
PrimeProducer(BlockingQueue<BigInteger> queue) {
this.queue = queue;
}
public void run() {
try {
BigInteger p = BigInteger.ONE;
while (!Thread.currentThread().isInterrupted())
queue.put(p = p.nextProbablePrime());
} catch (InterruptedException consumed) {
/* Allow thread to exit */
}
}
public void cancel() { interrupt(); }
}
Non-Interruptible Blocking
并不是所有blockingMethod都支持中断。例如input/outputStream这两个类,他们就不会抛出InterruptedException,也不会因为中断而直接返回。在Socket I/O而言,如果线程A关闭了Socket,那么正在socket上读写数据的B、C、D都会抛出SocketException. 非阻塞I/O(java.nio)也不支持interruptiable I/O,但是blocking operation可以通过关闭channel或者调用selector.wakeUp方法来操作。类似的是,内部锁(Synchronized Block)也不能被中断,但是ReentrantLock是支持可被中断模式的。
Non-Cancelable Tasks
有一些task设计出来就是不接受中断请求,但是即便如此,这些task也需要restore中断状态,以便higher-level的程序能够在这个task执行完毕后响应中断请求。
下面这段代码就是一个BlockingQueue.poll()忽略中断的的例子(和上面我的备注一样,不要在catch里面直接restore状态,不然queue.take()会造成无限循环。
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();
}
}
Summary
你可以利用interruption mechanism来提供一个灵活的取消策略。任务可以自行决定他们是否可以取消,或者如何响应中断请求,或者处理一些task relative cleanup。即便你想要忽略中断请求,你也需要restore中断状态,当你catchInterruptedException的时候,当higher不认识他的时候,就不要抛出啦。
如果你的代码在框架(包括JDK框架)中运行,那么interruptException你就务必像上面一样处理。如果单纯的你自己的小代码片段,那么你可以简单地认为InterruptException就是个bug。
在生产环境下,tomcat shutdown的时候,也会大量关闭线程池,发出中断请求。这个时候如果响应时间过于慢就会导致tomcat shutdown非常的慢(甚至于不响应)。所以大部分公司的重启脚本中都含有重启超时(例如20s)的一个强杀(kill -9)的兜底策略,这个过程对于程序来说就等于物理上的断电,凡是不可重试,没有断电保护,业务不幂等的情况都会产生大量的数据错误。
就现在业内的做法而言,大部分上述描述的问题几乎已经不再通过interrupt这种关闭策略来解决(因为实在难以解决),转而通过整体的系统架构模型来规避数据问题,例如数据库事务,例如可重试的幂等任务等等。
针对前端用户而言,就是ng的上下线心跳切换。但即使如此,对于请求已经进入tomcat线程池中的前端用户而言,还是会存在极其少量的服务器繁忙:)
Java Thread.interrupt interrupted的更多相关文章
- Java Thread.interrupt 害人! 中断JAVA线程(zz)
http://www.blogjava.net/jinfeng_wang/archive/2012/04/22/196477.html#376322 ————————————————————————— ...
- java中interrupt,interrupted和isInterrupted的区别
文章目录 isInterrupted interrupted interrupt java中interrupt,interrupted和isInterrupted的区别 前面的文章我们讲到了调用int ...
- Java Thread interrupt
现有线程对象threadA,调用threadA.interrupt(),则threadA中interrupted状态会被置成false,很多线程中都是通过isInterrupted()方法来检测线程是 ...
- java多线程 interrupt(), interrupted(), isInterrupted()方法区别
interrupt()方法: 作用是中断线程. 本线程中断自身是被允许的,且"中断标记"设置为true 其它线程调用本线程的interrupt()方法时,会通过checkAcces ...
- Thread interrupt() 线程中断的详细说明
GitHub源码地址 原创声明:作者:Arnold.zhao 博客园地址:https://www.cnblogs.com/zh94 一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止 ...
- Java thread中对异常的处理策略
转载:http://shmilyaw-hotmail-com.iteye.com/blog/1881302 前言 想讨论这个话题有一段时间了.记得几年前的时候去面试,有人就问过我一个类似的问题.就是j ...
- Thread interrupt方法解析
初步理解 我们在看一些多线程代码的时候,有的时候会碰到使用interrupt()方法的时候.从字面的意思来理解,应该就是中断当前正在执行的线程.那么,对于一个我们设计的普通线程来说,如果我们在主线程里 ...
- java thread reuse(good)
I have always read that creating threads is expensive. I also know that you cannot rerun a thread. I ...
- 注意Thread.interrupt()方法的真正作用并不是用来中断线程
程序是很简易的.然而,在编程人员面前,多线程呈现出了一组新的难题,如果没有被恰当的解决,将导致意外的行为以及细微的.难以发现的错误. 在本篇文章中,我们针对这些难题之一:如何中断一个正在 ...
随机推荐
- virtualbox安装增强功能并设置共享文件夹
virtualbox安装增强功能并设置共享文件夹 我们在安装之前,必须得先安装好它所需要的依赖包,不然安装过程必定会出现错误! 一.安装依赖包 #yum install kernel-headers# ...
- virtualbox centos安装增强工具和问题详解
virtualbox centos安装增强工具和问题详解 VirtualBox 大家都习惯性把它简称为 Vbox ,比 VM 的体积小.开源.速 度快.不过在使用 VirtualBox 在虚拟机中安装 ...
- Angular 4 重定向路由
重定向路由: 用户访问一个指定的地址时,将其重新向到另一个指定的地址 (接前面的一节) 配置如下: 之前http://localhost:4200 会进入主页,现在将主页导航改为home,http:/ ...
- python 如何将md5转为16字节
python的hashlib库中提供的hexdigest返回长度32的字符串. md5sum是128bit,也就是16字节,如何将python生成字符串的转为16字节呢? 请看下面代码 import ...
- github 改位置
在设置里改位置后,先在本地库上右键"stop track this repo". 然后在在线库重新CLONE.
- nsenter工具进入docker容器
对于运行在后台的Docker容器,我们经常需要做的事情是进入到容器中,docker为我们提供了docker exec .docker attach 命令,并且还提供了nsenter工具,外部工具供我们 ...
- unbound域名解析
安装unbound服务 # yum install unbound -y 开启服务 linux系统如何查看命令属于哪一个安装包 # yum provides */netstat 安装netstat命令 ...
- [UE4]C++中引用(&)的用法和应用实例
对于习惯使用C进行开发的朋友们,在看到c++中出现的&符号,可能会犯迷糊,因为在C语言中这个符号表示了取地址符,但是在C++中它却有着不同的用途,掌握C++的&符号,是提高代码执行效率 ...
- TensorFlow函数:tf.FIFOQueue队列
转载:https://blog.csdn.net/akadiao/article/details/78552037 tf.FIFOQueue tf.FIFOQueue继承基类QueueBase. Qu ...
- 高通QMI协议
QMI(Qualcomm MSM Interface,官方名称应该是Qualcomm Message Interface)是高通用来替代OneRPC/DM的协议,用来与modem通信. QMI协议定义 ...