一. 基础理解
- 先了解几个问题:
- 怎么中断一个运行中的线程
- 多线程中断标识是什么
- 中断后线程是不是就立刻停止运行了
- 注意: 一个线程不应该由其他线程来强制中断或停止,而是应该有线程自己自行停止,所以Thread.stop, Thread.suspend, Thread.resume都已经被废弃
- 每个线程中都有一个用于表示线程是否被中断的标识,当标识为true时说明被中断,false是表示未被中断,通过调用interrupt()方法设置,可以在别的线程中调用,也可以在别的线程中调用,
- 注意点这是java提供的一种中断线程的中断机制,interrupt()该方法也仅仅将线程对象设置为中断状态,实际线程并不能立即停止,在后续操作中需要不断的检测线程是否被中断
- 几个中断相关的api
如何退出一个线程
- 面试题: 如何优雅的退出或停止一个线程: 需要中断的线程不停监听中断标识状态,一旦判断为中断状态,则跳出逻辑,有一下几种方式
- volatile 方式
- AtomicBoolean
- Thread中自带的中断api
volatile 与 AtomicBoolean 中断线程示例
- volatile 示例
- 声明一个中断线程标识interruptFlags true为中断,使用 volatile 修饰
- 在 voiatileInterruptTest() 方法中开启了两个线程a与b,
- a线程执行时判断中断标识,如果为true说明要中断了,跳出业务
- b线程中修改中断标识,例如根据某种业务逻辑,判断a线程需要停止运行了,就设置标识为true,a线程判断状态为true,就跳出
import java.util.concurrent.TimeUnit;
public class InterruptDemo {
//是否中断某个线程的标识,使用volatile修饰
private volatile boolean interruptFlags = false;
public void voiatileInterruptTest() throws InterruptedException {
new Thread(() -> {
while (true) {
if (interruptFlags) {
System.out.println(Thread.currentThread().getName() + " 线程中断");
break;
}
System.out.println(Thread.currentThread().getName() + " 线程正常执行");
}
}, "a").start();
TimeUnit.SECONDS.sleep(5L);
new Thread(() -> {
interruptFlags = true;
System.out.println(Thread.currentThread().getName() + " 将线程a设置为中断状态");
}, "b").start();
}
public static void main(String[] args) throws InterruptedException {
InterruptDemo demo = new InterruptDemo();
demo.voiatileInterrupt();
}
}
- AtomicBoolean 示例
- 将中断线程标识设置为AtomicBoolean 原子类型变量
- a线程执行时先判断原子类型变量
- b线程中根据业务逻辑修改a线程的中断标识
//是否中断线程标识
private AtomicBoolean isStop = new AtomicBoolean(false);
public void atomiInterrupt() throws InterruptedException {
new Thread(() -> {
while (true) {
if (isStop.get()) {
System.out.println(Thread.currentThread().getName() + " 线程中断");
break;
}
System.out.println(Thread.currentThread().getName() + " 线程正常执行");
}
}, "a").start();
TimeUnit.SECONDS.sleep(5L);
new Thread(() -> {
isStop.set(true);
System.out.println(Thread.currentThread().getName() + " 将线程a设置为中断状态");
}, "b").start();
}
public static void main(String[] args) throws InterruptedException {
InterruptDemo demo = new InterruptDemo();
demo.atomiInterrupt();
}
- 总结: 不管是volatile 方式还是 Atomic原子类方式,1. 其底层关注点都是变量线程可见性的问题,一个线程中修改变量,另外一个线程中能及时拿到修改后的值, 2. 要中断的线程中要判断这个代表中断的标识,拿到状态,需要停止则跳出
Thread中自带的中断api示例
- 示例代码
- 主要通过 interrupt()设置线程中断标志位位中断状态true, isInterrupted()获取线程的中断标志位,如果是true并清除中断标识ture修改为false
- t1线程中执行业务逻辑时先isInterrupted()获取当前线程的中断标志位,如果为中断状态则跳出
- t2线程中根据业务逻辑,拿t1线程设置t1它自己为中断状态,
public void threadApiInterrupt() throws InterruptedException {
Thread t1 = new Thread(() -> {
while (true) {
//1.t1线程中判断自己线程的中断标志位,当为中断状态true时跳出执行
if (Thread.currentThread().isInterrupted()) {
System.out.println(Thread.currentThread().getName() + " 线程中断");
break;
}
System.out.println(Thread.currentThread().getName() + " 线程正常执行");
}
}, "t1");
t1.start();
TimeUnit.SECONDS.sleep(5L);
Thread t2 = new Thread(() -> {
//1.通过 t1 线程自己设置自己线程中断标志为true,中断状态
t1.interrupt();
System.out.println(Thread.currentThread().getName() + " 将线程a设置为中断状态");
}, "t2");
t2.start();
}
public static void main(String[] args) throws InterruptedException {
InterruptDemo demo = new InterruptDemo();
demo.threadApiInterrupt();
}
-
有个主意点: 在设置线程中断后,被设置中断的线程不会停止,只是将线程中断标志设置为true,没有其它任何操作,想让被设置的线程中断停止,需要程序员手动去实现,例如获取中断标志,手动break
- 并且: 假设线程处于被阻塞状态例如(sleep, wait, join等),此时如果对该线程调用interrupt()方法,会造成线程立即退出阻塞状态,并且抛出InterruptedException异常
阻塞状态线程中断时异常解决
- 上面说到了线程处于被阻塞状态例如(sleep, wait, join等),此时如果对该线程调用interrupt()方法,会造成线程立即退出阻塞状态,并且抛出InterruptedException异常, 在业务中也就是说当前这个线程是阻塞状态,然后一调用中断,由阻塞退出了,interrupt中断状态true变为false,并抛出了异常,这样就会造成: 1 原阻塞线程不再阻塞, 2. 中断标识由true变为了false
- 解决以上问题(在第3步中)
public void threadApiInterruptSleep() throws InterruptedException {
Thread t1 = new Thread(() -> {
while (true) {
//1.t1线程中判断自己线程的中断标志位,当为中断状态true时跳出执行
if (Thread.currentThread().isInterrupted()) {
System.out.println(Thread.currentThread().getName() + " 线程中断");
break;
}
System.out.println(Thread.currentThread().getName() + " 线程正常执行");
//2.t1线程中调用sleep阻塞
try {
TimeUnit.SECONDS.sleep(5L);
} catch (InterruptedException e) {
//3.防止阻塞中的线程调用interrupt()设置中断报异常,中断无效
//在catch中当前需要中断的线程再发起一次中断,调用interrupt()
Thread.currentThread().interrupt();
System.out.println(Thread.currentThread().getName()+"线程再次中断 ");
e.printStackTrace();
}
}
}, "t1");
t1.start();
TimeUnit.SECONDS.sleep(2L);
Thread t2 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " 将线程a设置为中断状态");
//2.通过 t1 线程自己设置自己线程中断标志为true,中断状态
t1.interrupt();
t1.isInterrupted();
}, "t2");
t2.start();
}
public static void main(String[] args) throws InterruptedException {
InterruptDemo demo = new InterruptDemo();
demo.threadApiInterruptSleep();
}
二. Thread中自带的中断底层分析
- 查看 interrupt() 设置线程为中断状态接口源码,发现调用到底层是native的interrupt0()方法
//1.
public void interrupt() {
if (this != Thread.currentThread())
checkAccess();
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
//实际调用了interrupt0()方法
interrupt0(); // Just to set the interrupt flag
b.interrupt(this);
return;
}
}
//实际调用了interrupt0()方法
interrupt0();
}
//2.查看interrupt0()发现该方法是native
private native void interrupt0();
- 查看 isInterrupted() 源码,其底层也是调用native的isInterrupted(boolean ClearInterrupted),并且会传递一个false,将中断标识设置为false
public boolean isInterrupted() {
return isInterrupted(false);
}
private native boolean isInterrupted(boolean ClearInterrupted);
三. 总结
- 以前设置中断线程的方法: volatile 或 AtomicBoolean 方式,都是基于变量可见性的特性: 例如 a线程执行时判断中断标识,如果为true说明要中断了,跳出业务, b线程中修改中断标识,例如根据某种业务逻辑,判断a线程需要停止运行了,就设置标识为true,a线程判断状态为true,就跳出
- 讲一下规范中有个要求: 一个线程不应该由其他线程来强制中断或停止,而是应该有线程自己自行停止,所以Thread.stop, Thread.suspend, Thread.resume都已经被废弃
- Thread中自带的中断api: 通过 interrupt()设置线程中断标志位位中断状态true, isInterrupted()获取线程的中断标志位,如果是true并清除中断标识ture修改为false, 例如
- t1线程中执行业务逻辑时先isInterrupted()获取当前线程的中断标志位,如果为中断状态则跳出
- t2线程中根据业务逻辑,拿t1线程设置t1它自己为中断状态,
- 使用interrupt()方法中断线程的注意点:
- 线程并不会自动中断,需要调用isInterrupted()获取状态,手动去break
- 如果线程处于被阻塞状态例如(sleep, wait, join等),对该线程调用interrupt()方法,会造成线程立即退出阻塞状态,并抛出InterruptedException异常
- 如何解决中断阻塞状态线程抛出异常问题: tryCatch捕获InterruptedException异常,当发生该异常时在catch中再次调用interrupt()设置中断标识