当其他线程调用当前线程的interrupt方法,表示向当前线程打个招呼,告诉他可以中断线程的执行了,至于什么时候中断则取决于当前线程自己。
如何关闭一个线程
线程在什么情况下需要关闭?(持续运行、死循环:while(true)。阻塞:sleep/wait。)
有人会说使用stop方法。stop方法可以关闭一个线程。类似于 kill -9 ID 。但是形式上并不友好,如果线程正在执行任务,中断会导致不可预测的问题。
那么如何友好的关闭一个线程呢?我们应该见过这种写法。
通过变量控制结束线程
private volatile static boolean stop = false;
private static int i = 0;
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()->{
while (!stop()){
i++;
}
System.out.println(i);
},"ParamThread");
thread.start();
Thread.sleep(2000);
stop = true;
}
在两秒过后线程会自动终止。
Thread.interrupet
interrupet 原理也是通过变量来控制线程的是否结束。他只是一种友好的唤醒中断。分为两种情况。
-
非阻塞状态。
因为thread.interrupt()会修改isInterrupted()的值为true,所以在while循环判断时自然可以中断。
-
阻塞状态。
在线程Sleep进入阻塞状态后,会抛出InterruptedException,这时调用thread.interrupt()会唤醒线程进入InterruptedException,异常捕获处理异常,并且会重置isInterrupted()状态为false。所以此时如果不做操作,线程仍然会继续运行。如果想要中断需要在异常捕获时在此调用interrupt()方法。
private static int i = 0;
// 此方法部分情况会出现 Thread.currentThread().isInterrupted() 一直为 false 情况 可能跟JVM环境有关系
// 这里只需要知道
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()->{
//判断线程是否需要中断 默认值是 false interrupt方法后会变为true
while (!Thread.currentThread().isInterrupted()){
i++;
}
System.out.println(i);
},"ParamThread");
thread.start();
Thread.sleep(2000);
thread.interrupt();//友好的中断 意味着等待线程run方法结束才会中断线程
}
结果同样会在两秒钟后结束。
再看如下代码(阻塞状态)。
private static int i = 0;
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()->{
i++;
try {
Thread.sleep(5000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i);
},"ParamThread");
thread.start();
Thread.sleep(2000);
thread.interrupt();//友好的中断 意味着等待线程run方法结束才会中断线程
}
结果同样会在两秒就结束线程,而不会等待5000秒后才结束。
综上所属,interrupt方法会唤醒处于阻塞状态的线程(抛出 InterruptedException ),使线程继续往下走,直至线程run方法执行完毕,才会中断线程。如后续又阻塞方法仍会继续阻塞。
在两种结合的情况下回出现一种情况,无法退出循环
private static int i = 0;
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()->{
//判断线程是否需要中断 默认值是 false interrupt方法后会变为true
while (!Thread.currentThread().isInterrupted()){
i++;
System.out.println(Thread.currentThread().getName()
+"持续执行中,当前第"+i+"次.");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
//注意 此处不做确认中断处理的话。会自动触发会复位操作(isInterrupted 依然会等于 false)
//如果要确认中断 需要再次执行 interrupt()方法
//Thread.currentThread().interrupt();
e.printStackTrace();
}
}
System.out.println(i);
},"ParamThread");
thread.start();
Thread.sleep(2000);
thread.interrupt();//友好的中断 意味着等待线程run方法结束才会中断线程
}
如果要确认中断 需要再次执行 interrupt()方法
Thread.currentThread().interrupt();
思考
private volatile static boolean stop = false;
private static int i = 0;
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()->{
while (!Thread.currentThread().isInterrupted()){
i++;
System.out.println(Thread.currentThread().getName()
+"持续执行中,当前第"+i+"次.");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
//1 此处加上断点 会触发么?
//2 在此处调用thread.Interrupted() 复位操作,结果会中断么?
System.out.println("1轮Sleep阻塞中");
e.printStackTrace();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
//3 此处加上断点 会触发么?
Thread.currentThread().interrupt();
System.out.println("2轮Sleep阻塞中");
e.printStackTrace();
}
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
//4 此处加上断点 会触发么?
//5 在此处调用thread.Interrupted() 复位操作,结果会中断么?
System.out.println("3轮Sleep阻塞中");
e.printStackTrace();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("4轮Sleep阻塞中");
e.printStackTrace();
}
}
System.out.println(i);
},"ParamThread");
thread.start();
Thread.sleep(1000);
thread.interrupt();//友好的中断 意味着等待线程run方法结束才会中断线程
}
查看上述代码,有5个问题需要回答。
- 关于触发哪个断点,取决于线程目前执行的位置。由于线程是在start()后,sleep(1000)才调用interrupt()方法。所以此时线程处于第二轮阻塞中。
- 线程在第二轮阻塞中,触发断点后,由于下方使用了确认中断方法(等同于再次调用interrupt()方法,因此线程继续往下走,在触发sleep(20000)后会直接进入catch状态),所以会触发第三轮阻塞。
- 在第三轮阻塞中没有继续中断线程,所以会自动触发interrupted复位,线程不会结束。如果想要结束在后续抛出Interrupted Exception 异常处都需要 调用 interrupt()方法。
- 如果第一轮阻塞处sleep(2000),意味着首次interrupt()时会触发第一轮阻塞,且第一轮阻塞没有再次调用interrupt()方法,所以第二轮、第三轮都不会再触发。
总结
- interrupt方法是一个友好中断(死循环、阻塞等运行时长不可控状态)线程的方法(线程run方法必须执行完毕)。
- interrupted方法可以复位调用interrupt方法(Interrupted Exception 不做处理等价于 调用interrupted方法)。
- 如果一个线程中抛出多个InterruptedException方法,且希望在调用interrupt方法后关闭线程,则需要在每一处做确认关闭操作(Thread.currentThread().interrupt())
- 每次调用interrupt()方法,仅会触发一次唤醒操作。
备注
try {
// 可以理解为该方法是主动检查是否有被中断
lock.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.lockInterruptibly 也可以响应中断状态。以ReenTrantLock为例:
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
public final void acquireInterruptibly(int arg)
throws InterruptedException {
// 如果已被中断 抛出InterruptedException异常
if (Thread.interrupted())
throw new InterruptedException();
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
案例
如何响应中断
日常我们都会用到定时任务,定时任务通常都会提供任务终止的能力。
我们可以这么理解,对于定时任务执行过程来说,就是一个独立运行的线程,我们通过定时任务的管理功能页面,点击终止,就可以理解为调用了定时任务执行线程的interrupt()方法。此时如果线程执行过程中,可以响应中断,那么便可以停止任务。那么什么样的才算可以响应中断呢?
示例1:
线程进入等待状态。你在休息没有事情做时,我可以找你(中断你的休息)。但是如果你在忙,那就不会去打扰你了。
try {
Thread.sleep(5000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
示例2:
线程通过中断状态来控制是否结束。
while (!Thread.currentThread().isInterrupted()){
i++;
}
示例3:
使用lock.lockInterruptibly()方法。但是此方法有一个弊端。如果在这个方法执行完以后,才触发的中断,就无法进行响应。比如下方代码,在有while(true) 的情况下,能够确保每次都会执行lockInterruptibly()检查中断状态。但是如果没有while(true),并且是在程序执行完lockInterruptibly()检查后,比如执行到i++之后才触发了中断,那么便无法响应。
因此,响应中断是有前提条件的,毕竟是属于友好的中断。
while (true){
try{
lock.lockInterruptibly();
}catch (InterruptedException e){
e.printStackTrace();
break;
}
i++;
}
因此,加入我们自己要做一个定时任务,并提供中断的能力,为了安全,可以采用示例的方式,在代码中加入中断响应。
响应中断带来的问题
响应中断,是等到线程执行完毕后才会终止线程。属于友好的停止线程方式。
基于上边定时任务的例子,可以理解为是任务执行完毕后才会中断线程,或者程序执行逻辑中有响应中断,并有相对应的处理措施。比如,要刷一批数据,数据较多时,我们会分批处理while/for循环,同时为了响应中断,我们可以将条件控制为while(逻辑条件 && 中断状态)。这时,如果触发了中断,我们便可以在保证一次循环执行完毕后,停止后续任务执行。不会导致执行一半,数据不完整情况。
所以响应中断,天然就不会有太大的风险。(除非使用不当,不该响应中断的地方,响应了中断)
响应中断,最根本的问题就是会抛出 InterruptedException
对于异常监控,要求比较严格的公司(出现一个线上异常,就要跟大佬解释一遍原因,做一个复盘的,出一个SOP的请慎重!!!)。
在我们使用这种情况处理中断时,不要认为这种不会抛出异常。
while (!Thread.currentThread().isInterrupted()){
// TODO ...
}
比如:如果我们有请求数据库的操作,以DruidDataSource为例,获取连接的方法有响应中断... 此时只要被中断,就会出现 InterruptedException ...
// com.alibaba.druid.pool.DruidDataSource#getConnectionInternal
private DruidPooledConnection getConnectionInternal(long maxWait) throws SQLException {
// ...
try {
lock.lockInterruptibly();
} catch (InterruptedException e) {
connectErrorCountUpdater.incrementAndGet(this);
throw new SQLException("interrupt", e);
}
// ...
}
如上:一个 SQLException 让你措手不及。