前言
本文隶属于专栏《100个问题搞定Java并发》,该专栏为笔者原创,引用请注明来源,不足和错误之处请在评论区帮忙指出,谢谢!
本专栏目录结构和参考文献请见100个问题搞定Java并发
正文
在Java中,线程中断是一种重要的线程协作机制。从表面上理解,中断就是让目标线程停止执行的意思,实际上并非完全如此。
在专栏前面我们提到了 stop() 方法停止线程的坏处,详情请见——Java中有哪些方法可以终止线程运行?
在 JDK 中是否有提供完善的支持线程退出的能力呢?答案是肯定的,那就是线程中断。
严格地讲,线程中断并不会使线程立即退出,而是给线程发送一个通知,告知目标线程,有人希望你退出啦!
至于目标线程接到通知后如何处理,则完全由目标线程自行决定这点很重要,如果中断后,线程立即无条件退出,我们就又会遇到 stop 方法的老问题。
有三个方法与线程中断有关,这三个方法看起来很像,可能会引起混淆和误用,希望大家注意。
这三个方法都来自于 java.lang.Thread
public void interrupt(); //中断线程
public boolean isInterrupted(); //判断当前线程是否已经被中断
public static boolean interrupted(); //判断当前线程是否已经被中断,并且清除当前中断状态
Thread.interrupt() 方法是一个实例方法。 它通知目标线程中断,也就是设置中断标志位。 中断标志位表示当前线程已经被中断了。
Thread.isInterrupted() 方法也是实例方法,它判断当前线程是否被中断(通过检査中断标志位)。
最后的静态方法 Thread.interrupted() 也可用来判断当前线程的中断状态,但同时会清除当前线程的中断标志位状态。
Thread.currentThread.interrupt();
package com.shockang.study.java.concurrent.thread.interrupt;
/**
* sleep中断后 抛出异常被重置中断状态
* 如果希望sleep后可以判断中断状态,则必须在sleep的异常处理中,在设置中断
*
* @author Administrator
*/
public class InterruptSleepThread {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread() {
@Override
public void run() {
while (true) {
if (Thread.currentThread().isInterrupted()) {
System.out.println("Interrupted!");
break;
}
try {
Thread.sleep(2000);
// 推荐使用 TimeUnit.MILLISECONDS.sleep(2000);
} catch (InterruptedException e) {
System.out.println("Interrupted When Sleep");
//设置中断状态
Thread.currentThread().interrupt();
}
Thread.yield();
}
}
};
t1.start();
Thread.sleep(2000);
t1.interrupt();
}
}
在 catch 子句部分,由于已经捕获了中断,我们可以立即退出线程。
但在这里,我们并没有这么做,因为也许在这段代码中,我们还必须进行后续的处理来保证数据的一致性和完整性,
因此,执行了 Thread.interrupt 方法再次中断自己,置上中断标记位。
只有这么做,在前面的中断检査中,才能发现当前线程已经被中断了。
注意: Thread.sleep() 方法由于中断而抛出异常,此时,它会清除中断标记,如果不加处理,那么在下一次循环开始时,就无法捕获这个中断,故在异常处理中,再次设置中断标记位。
java.lang.Thread 源码解析 (JDK8)
/**
* 中断此线程。
*
* 除非当前线程正在中断自身(这是始终允许的),否则将调用此线程的checkAccess方法,这可能会导致抛出SecurityException。
*
* 如果此线程在调用对象类的 wait()、wait(long) 或wait(long, int) 方法时被阻塞,
*
* 或者在调用此类的join()、join(long)、join(long, int)、sleep(long) 或sleep(long, int)方法时被阻塞,
*
* 则其中断状态将被清除,并且它将接收到InterruptedException。
*
* 如果此线程在I/O操作中被阻塞在中断通道上,则通道将被关闭,线程的中断状态将被设置,并且线程将接收java.nio.channels.ClosedByInterruptException。
*
* 如果该线程在java.nio.channels.Selector中被阻塞,那么该线程的中断状态将被设置,并且它将立即从选择操作返回,可能返回一个非零值,就像调用了选择器的唤醒方法一样。
*
* 如果前面的条件都不成立,那么这个线程的中断状态将被设置。
*
* 中断一个非活动线程不需要有任何效果。
*
* @throws SecurityException–如果当前线程无法修改此线程
*
*/
public void interrupt() {
if (this != Thread.currentThread())
checkAccess();
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0(); // Just to set the interrupt flag
b.interrupt(this);
return;
}
}
interrupt0();
}
/**
* 测试此线程是否已中断。
*
* 线程的中断状态不受此方法的影响。
*
* 由于线程在中断时不活动而被忽略的线程中断将由返回false的方法反映。
*
* @return 如果此线程已中断,则为true;否则为假。
*/
public boolean isInterrupted() {
return isInterrupted(false);
}
/**
* 测试某个线程是否被中断。中断状态是否重置取决于传递的ClearInterrupted的值
*/
private native boolean isInterrupted(boolean ClearInterrupted);
/**
* 测试当前线程是否已中断。
*
* 此方法清除线程的中断状态。
*
* 换句话说,如果这个方法被连续调用两次,那么第二个调用将返回false
*
* (除非当前线程在第一个调用清除其中断状态之后,第二个调用检查它之前再次中断)。
*
* 由于线程在中断时不活跃而被忽略的线程中断,将由这个方法返回false
*
* @return true 当前线程成功被中断
* false 当前线程未被中断
*/
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}