JUC 六. 线程中断 与 LockSupport

一. 基础理解

  1. 先了解几个问题:
  1. 怎么中断一个运行中的线程
  2. 多线程中断标识是什么
  3. 中断后线程是不是就立刻停止运行了
  1. 注意: 一个线程不应该由其他线程来强制中断或停止,而是应该有线程自己自行停止,所以Thread.stop, Thread.suspend, Thread.resume都已经被废弃
  2. 每个线程中都有一个用于表示线程是否被中断的标识,当标识为true时说明被中断,false是表示未被中断,通过调用interrupt()方法设置,可以在别的线程中调用,也可以在别的线程中调用,
  3. 注意点这是java提供的一种中断线程的中断机制,interrupt()该方法也仅仅将线程对象设置为中断状态,实际线程并不能立即停止,在后续操作中需要不断的检测线程是否被中断
  4. 几个中断相关的api
    在这里插入图片描述
    在这里插入图片描述

如何退出一个线程

  1. 面试题: 如何优雅的退出或停止一个线程: 需要中断的线程不停监听中断标识状态,一旦判断为中断状态,则跳出逻辑,有一下几种方式
  1. volatile 方式
  2. AtomicBoolean
  3. Thread中自带的中断api

volatile 与 AtomicBoolean 中断线程示例

  1. volatile 示例
  1. 声明一个中断线程标识interruptFlags true为中断,使用 volatile 修饰
  2. 在 voiatileInterruptTest() 方法中开启了两个线程a与b,
  3. a线程执行时判断中断标识,如果为true说明要中断了,跳出业务
  4. 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();
    }
}
  1. AtomicBoolean 示例
  1. 将中断线程标识设置为AtomicBoolean 原子类型变量
  2. a线程执行时先判断原子类型变量
  3. 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();
    }
  1. 总结: 不管是volatile 方式还是 Atomic原子类方式,1. 其底层关注点都是变量线程可见性的问题,一个线程中修改变量,另外一个线程中能及时拿到修改后的值, 2. 要中断的线程中要判断这个代表中断的标识,拿到状态,需要停止则跳出

Thread中自带的中断api示例

  1. 示例代码
  1. 主要通过 interrupt()设置线程中断标志位位中断状态true, isInterrupted()获取线程的中断标志位,如果是true并清除中断标识ture修改为false
  2. t1线程中执行业务逻辑时先isInterrupted()获取当前线程的中断标志位,如果为中断状态则跳出
  3. 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();
    }
  1. 有个主意点: 在设置线程中断后,被设置中断的线程不会停止,只是将线程中断标志设置为true,没有其它任何操作,想让被设置的线程中断停止,需要程序员手动去实现,例如获取中断标志,手动break
  2. 并且: 假设线程处于被阻塞状态例如(sleep, wait, join等),此时如果对该线程调用interrupt()方法,会造成线程立即退出阻塞状态,并且抛出InterruptedException异常

阻塞状态线程中断时异常解决

  1. 上面说到了线程处于被阻塞状态例如(sleep, wait, join等),此时如果对该线程调用interrupt()方法,会造成线程立即退出阻塞状态,并且抛出InterruptedException异常, 在业务中也就是说当前这个线程是阻塞状态,然后一调用中断,由阻塞退出了,interrupt中断状态true变为false,并抛出了异常,这样就会造成: 1 原阻塞线程不再阻塞, 2. 中断标识由true变为了false
  2. 解决以上问题(在第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中自带的中断底层分析

  1. 查看 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();
  1. 查看 isInterrupted() 源码,其底层也是调用native的isInterrupted(boolean ClearInterrupted),并且会传递一个false,将中断标识设置为false
	public boolean isInterrupted() {
        return isInterrupted(false);
    }
	private native boolean isInterrupted(boolean ClearInterrupted);

三. 总结

  1. 以前设置中断线程的方法: volatile 或 AtomicBoolean 方式,都是基于变量可见性的特性: 例如 a线程执行时判断中断标识,如果为true说明要中断了,跳出业务, b线程中修改中断标识,例如根据某种业务逻辑,判断a线程需要停止运行了,就设置标识为true,a线程判断状态为true,就跳出
  2. 讲一下规范中有个要求: 一个线程不应该由其他线程来强制中断或停止,而是应该有线程自己自行停止,所以Thread.stop, Thread.suspend, Thread.resume都已经被废弃
  3. Thread中自带的中断api: 通过 interrupt()设置线程中断标志位位中断状态true, isInterrupted()获取线程的中断标志位,如果是true并清除中断标识ture修改为false, 例如
  1. t1线程中执行业务逻辑时先isInterrupted()获取当前线程的中断标志位,如果为中断状态则跳出
  2. t2线程中根据业务逻辑,拿t1线程设置t1它自己为中断状态,
  1. 使用interrupt()方法中断线程的注意点:
  1. 线程并不会自动中断,需要调用isInterrupted()获取状态,手动去break
  2. 如果线程处于被阻塞状态例如(sleep, wait, join等),对该线程调用interrupt()方法,会造成线程立即退出阻塞状态,并抛出InterruptedException异常
  1. 如何解决中断阻塞状态线程抛出异常问题: tryCatch捕获InterruptedException异常,当发生该异常时在catch中再次调用interrupt()设置中断标识
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值