文章目录
1 线程打断(interrupt)
// 打断线程
public void interrupt();
// 是否打断
public static boolean interrupted();
// 是否打断
public boolean isInterrupted();
1.1 interrupt()方法
很多使线程阻塞的方法都会抛出InterruptedException异常,这个异常就是当线程进入阻塞的时候,调用了interrupt方法就会
抛出InterruptedException异常。这些方法有:
Object的wait方法。
Object的wait(long)方法。
Object的wait(long,int)方法。
Thread的sleep(long)方法。
Thread的sleep(long,int)方法。
Thread的join方法。
Thread的join(long)方法。
Thread的join(long,int)方法。
InterruptibleChannel的io操作。
Selector的wakeup方法。
。。。
- 上述方法都会使得当前线程进入阻塞状态,若另外的一个线程调用被阻塞线程的interrupt方法,则会打断这种阻塞,抛出InterruptedException
- 这个异常就像一个signal(信号)一样通知当前线程被打断了
- 打断一个线程并不等于该线程的生命周期结束,仅仅是打断了当前线程的阻塞状态。
演示:
public static void main(String[] args) throws InterruptedException {
// 创建一个线程
Thread thread = new Thread(()->{
try {
// 线程休眠5s,输出hello world
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + "捕获到打断信号");
}
System.out.println("Hello World");
},"MyThread");
thread.start();
// 主线程休眠2s后,调用MyThread的interrupt方法吗,打断MyThread
TimeUnit.SECONDS.sleep(2);
thread.interrupt();
}
MyThread捕获到打断信号
Hello World
会发现MyThread并没有休眠够5秒钟,就被主线程打断了,从休眠中醒来,继续执行了
interrupt这个方法到底做了什么样的事情呢?
- 在一个线程内部存在着名为interrupt flag的标识,如果一个线程被interrupt,那么它的flag将被设置true。
- 但是如果当前线程正在执行可中断方法被阻塞时,调用interrupt方法将其中断,反而会导致flag被清除。(!!!)
- 另外有一点需要注意的是,如果一个线程已经是死亡状态,那么尝试对其的interrupt会直接被忽略。
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();
}
1.2 isInterrupted方法
isInterrupted是Thread的一个成员方法,它主要判断当前线程是否被中断,该方法仅仅是对interrupt标识的一个判断,并不会影响标识发生任何改变。
测试:在一个线程内部存在着名为interrupt flag的标识,如果一个线程被interrupt,那么它的flag将被设置true
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while (true) {
// do nothing, just empty loop
}
});
thread.setDaemon(true);
thread.start();
TimeUnit.SECONDS.sleep(2);
System.out.println("Thread is interrupted: " + thread.isInterrupted());
thread.interrupt();
System.out.println("Thread is interrupted: " + thread.isInterrupted());
}
上面的代码中定义了一个线程,并且在线程的执行单元中(run方法)写了一个空的死循环,为什么不写sleep呢?因为sleep是可中断方法,会捕获到中断信号,从而干扰我们程序的结果。下面是程序运行的结果,记得手动结束上面的程序运行,或者你也可以将上面定义的线程指定为守护线程,这样就会随着主线程的结束导致JVM中没有非守护线程而自动退出。
运行结果:
Thread is interrupted: false
Thread is interrupted: true
可见当我们调用线程的interrupt方法之后,就会把线程的interrupt标识设置为true
测试:如果当前线程正在执行可中断方法被阻塞时,调用interrupt方法将其中断,反而会导致flag被清除
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while (true) {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
System.out.println("捕获到打断信号,打断标识被清除; Thread is interrupted: " + Thread.currentThread().isInterrupted());
}
}
});
thread.setDaemon(true);
thread.start();
TimeUnit.SECONDS.sleep(2);
System.out.println("Thread is interrupted: " + thread.isInterrupted());
thread.interrupt();
System.out.println("Thread is interrupted: " + thread.isInterrupted());
}
输出:
Thread is interrupted: false
Thread is interrupted: true
捕获到打断信号,打断标识被清除; Thread is interrupted: false
可见当前线程正在执行可中断方法被阻塞时,调用interrupt方法将其中断,反而会导致flag被清除
其实这也不难理解,可中断方法捕获到了中断信号之后,为了不影响线程中其他方法的执行,将线程的interrupt标识复位是一种很合理的设计。
1.3 interrupted()方法
interrupted是一个静态方法
,虽然其也用于判断当前线程是否被中断,但是它和成员方法isInterrupted还是有很大的区别的:
- 调用该方法会直接擦除掉线程的interrupt标识。
- 需要注意的是,如果当前线程被打断了,那么第一次调用interrupted方法会返回true,并且立即擦除了interrupt标识;第二次包括以后的调用永远都会返回false,除非在此期间线程又一次地被打断
验证一下上面的这句话:
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread() {
@Override
public void run() {
while (true) {
System.out.println(Thread.interrupted());
}
}
};
thread.setDaemon(true);
thread.start();
//shortly block make sure the thread is started.
TimeUnit.MILLISECONDS.sleep(2);
thread.interrupt();
}
同样由于不想要受到可中断方法如sleep的影响,在Thread的run方法中没有进行任何短暂的休眠,所以运行上面的程序会出现非常多的输出,但是我们通过对输出的检查会发现如下所示的内容,其足以作为对该方法的解释。
false
false
false
false
true
false
false
false
false
false
false
false
在很多的false包围中发现了一个true,也就是interrupted方法判断到了其被中断,立即擦除了中断标识,并且只有这一次返回true,后面的都将会是false。
1.4 是否擦除线程interrupt的标识
翻看源码,其实内部都是调用的同一个本地方法,只是参数不同
public boolean isInterrupted() {
return isInterrupted(false);
}
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
// 其实内部都是调用的同一个本地方法,只是参数不同
private native boolean isInterrupted(boolean ClearInterrupted);
ClearInterrupted这个参数:主要用来控制是否擦除线程interrupt的标识
- isInterrupted方法的源码中该参数为false,表示不想擦除
- interrupted方法的源码中该参数为true,表示擦除
1.5 打断线程并不是终止线程
@Slf4j
public class InterruptTest1 {
public static void main(String[] args) {
// 定义一个线程
Thread thread = new Thread("MyThread"){
@Override
public void run() {
while (true){
}
}
};
thread.start();
log.info("{} 是否已经打断: {}",thread.getName(),thread.isInterrupted());
// 打断MyThread线程
thread.interrupt();
log.info("{} 是否已经打断: {}",thread.getName(),thread.isInterrupted());
// 虽然这里MyThread线程被打断了,依然在运行
}
}
1.6 提前打断
运行下面代码,观察结果。
public static void main(String[] args){
System.out.println("Main thread is interrupted? " + Thread.interrupted());
Thread.currentThread().interrupt();
System.out.println("Main thread is interrupted? " + Thread.currentThread().isInterrupted());
System.out.println(LocalDateTime.now());
try{
TimeUnit.MINUTES.sleep(5);
} catch (InterruptedException e){
System.out.println(LocalDateTime.now());
System.out.println("I will be interrupted still.");
}
}
Main thread is interrupted? false
Main thread is interrupted? true
2021-03-29T15:12:28.468
2021-03-29T15:12:28.469
I will be interrupted still.
发现这两个时间的差根本不足5s,说明在前打断之后,执行到sleep之后,会立马捕获到这个打断标记,不再休眠
也就是说:如果一个线程设置了interrupt标识,那么接下来的可中断方法会立即中断