JUC进阶之路-线程中断机制

401 阅读5分钟

从阿里蚂蚁金服面试题说起

image.png

如何停止/中断一个运行中的线程

基本概念

什么是中断(中断协商机制)?

  1. 首先一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止(所以stop()/suspend()/resume()都已经弃用)
  2. 其次在Java中没有办法立即停止一条线程,然而停止线程却显得尤为重要,如取消一个耗时操作,因此Java提供了一种用于停止线程的机制--中断
  3. 中断只是一种协作机制,Java没有给中断增加任何语法,中断的过程完全需要程序员自己实现(如果要中断一个线程,需要手动调动该线程的interrupt方法,改方法也仅仅是降线程对象的中断标识设为true;接着你需要自己写代码不断的检测当前线程的标识位,如果为true,表示别的线程要求这条线程中断)
  4. 每个线程对象都有一个标识,用于标识线程是否被中断;该标识位为true表示中断,为false表示未中断; 通过调用线程对象的interrupt方法将该线程的标识位设为true;可以在别的线程中调用,也可以在自己的线程中调用

中断相关的Api方法

方法解释
void interrupt()实例方法interrupt()仅仅是设置线程的中断状态为true,不会停止线程
boolean interrupted()静态方法,Thread.interrupted()
判断线程是否被中断,并清除当前中断状态
这个方法做了两件事情
1.返回当前线程的中断状态
2.将当前线程的中断状态为false
boolean isInterrupted()判断当前线程是否被中断(通过检查中断标志位)

面试题解答

  1. 思考:在需要中断的线程中不断监听中断状态,一旦发生中断,就执行相应的中断处理业务逻辑
  2. 具体实现
  • 通过一个volatile变量实现
    • 代码实现
    static volatile Boolean isStop = false;
    public static void main(String[] args) {
        new Thread(() -> {
            while (true) {
                if (isStop) {
                    System.out.println("小陀螺,转呀转,转不动了 ---> isStop的值" + isStop);
                    break;
                }
                System.out.println("我是优秀的小陀螺,转呀转 ---> isStop的值" + isStop);
            }

        }, "t1").start();
        //睡眠三秒
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (Exception e) {
            e.printStackTrace();
        }
        new Thread(() -> {
            //将isStop置为true
            isStop = true;
        }, "t2").start();
    }
  • 通过AtomicBoolean实现
    static AtomicBoolean atomicBoolean = new AtomicBoolean(false);
    public static void main(String[] args) {
        new Thread(() -> {
            while (true) {
                if (atomicBoolean.get()) {
                    System.out.println("小陀螺,转呀转,转不动了 ---> atomicBoolean的值" + atomicBoolean);
                    break;
                }
                System.out.println("我是优秀的小陀螺,转呀转 ---> atomicBoolean的值" + atomicBoolean);
            }
        }, "t1").start();
        //睡眠三秒
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (Exception e) {
            e.printStackTrace();
        }
        new Thread(() -> {
            //将atomicBoolean置为true
            atomicBoolean.set(true);
        }, "t2").start();
    }
  • 通过Thread类自带的中断API方法实现
public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            while (true) {
                if (Thread.currentThread().isInterrupted()) {
                    //当中断标志为true
                    System.out.println("小陀螺,转呀转,转不动了 ---> 中断标志的值" + Thread.currentThread().isInterrupted());
                    break;
                }
                System.out.println("我是优秀的小陀螺,转呀转 ---> 中断标志的值" + Thread.currentThread().isInterrupted());
            }
        }, "t1");
        t1.start();
        //睡眠三秒
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (Exception e) {
            e.printStackTrace();
        }
        new Thread(() -> {
            //设置中断标志位为true
            t1.interrupt();
        }, "t2").start();
    }

interrupt()源码解析

 public void interrupt() {
        if (this != Thread.currentThread())
            checkAccess();

        synchronized (blockerLock) {
            Interruptible b = blocker;
            if (b != null) {
                // Just to set the interrupt flag 仅仅是设置了一个中断标志位 调用的本地native方法
                interrupt0();           
                b.interrupt(this);
                return;
            }
        }
        interrupt0();
    }
    
    //本地native方法
    private native void interrupt0();

isInterrupted()源码解析

    public boolean isInterrupted() {
        return isInterrupted(false);
    }
    
    //本地native 方法  ClearInterrupted 是否清除中断标志位
    private native boolean isInterrupted(boolean ClearInterrupted);

面试题:当前线程的中断标识为true,是不是就立刻停止?

  1. 线程调用interrupt时:
    • 如果线程处于正常活动状态,那么会将该线程的中断标志位设置为true,仅此而已。被设置中断标志位的线程将继续正常运行,不受影响。所以interrupt并不能真正的中断线程,需要被调用的线程自己进行配合才行
    • 如果处于被阻塞状态(sleep wait Join等状态),在别的线程中调用当前线程对象的interrupt方法,那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常
  2. 案例一:中断为true后,并不是立刻stop程序
    • 代码
     public static void main(String[] args) {
            Thread t1 = new Thread(() -> {
                for (int i = 0; i <= 300; i++) {
                    System.out.println("i --->" + i);
                }
                System.out.println("调用interrupt()方法后,t1第二次的中断标识为值: --->" + Thread.currentThread().isInterrupted());
            }, "t1");
            t1.start();
            System.out.println("调用interrupt()方法前,t1的中断标识为值: --->" + Thread.currentThread().isInterrupted());
            //睡眠2毫秒
            try {
                TimeUnit.MICROSECONDS.sleep(2);
            } catch (Exception e) {
                e.printStackTrace();
            }
            //实例方法interrupt仅仅是设置线程的中断状态位置为true,不会停止线程
            t1.interrupt();
            //活动状态,t1线程还在执行中
            System.out.println("调用interrupt()方法后,t1第一次的中断标识为值: --->" + Thread.currentThread().isInterrupted());
            try {
                TimeUnit.MICROSECONDS.sleep(3000);
            } catch (Exception e) {
                e.printStackTrace();
            }
            //非活动状态,t1线程不在执行中,已经结束执行了
            System.out.println("调用interrupt()方法后,t1第三次的中断标识为值: --->" + Thread.currentThread().isInterrupted());
        }
    
    • 输出结果

image.png

image.png

image.png

  • 结论:interrupt调用之后仅仅是设置中断标志为true,不会立即停止线程
  1. 案例三:
    • 代码
 /**
   * sleep方法抛出异常InterruptedException后,中断标识也被清空置为false,
   * 我们在catch没有通过调用interrupt()方法再次将中断标识置为true,这样就导致了无限循环了
   */
public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            while (true) {
                if (Thread.currentThread().isInterrupted()) {
                    //当中断标志为true
                    System.out.println(Thread.currentThread().getName() + "---> 中断标志的值: ---> " + Thread.currentThread().isInterrupted() + " ---> 自己退出了 ");
                    break;
                }
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    //不执行此行代码,程序就不会停止,中断不打断
                    //Thread.currentThread().interrupt();
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "---> 中断标志的值: ---> " + Thread.currentThread().isInterrupted());
            }
        }, "t1");
        t1.start();
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (Exception e) {
            e.printStackTrace();
        }
        t1.interrupt();
    }
  • 输出结果

image.png

  • 得出结论:如果在调用Object类的wait(),wait(long)或wait(long, int)方法或join(),join(long),join(long, int)方法时阻塞了该线程,sleep(long)或sleep(long, int)此类的方法,则其中断状态将被清除,并将收到InterruptedException

image.png

  1. 总结:中断只是一种协同机制,修改中断标识位仅此而已,不会立刻stop打断

interrupted()方法

  1. 代码
     public static void main(String[] args) {
        System.out.println("调用interrupt()方法前的状态01: ---> " + Thread.interrupted());
        System.out.println("调用interrupt()方法前的状态02: ---> " + Thread.interrupted());
        System.out.println("调用interrupt()方法前");
        Thread.currentThread().interrupt();
        System.out.println("调用interrupt()方法后");
        System.out.println("调用interrupt()方法后的状态03: ---> " + Thread.interrupted());
        System.out.println("调用interrupt()方法后的状态04: ---> " + Thread.interrupted());
    }
  1. 输出结果

image.png

  1. 得出结论:1.返回当前线程的中断状态 2.将当前线程的中断状态为false

  2. 源码解析 image.png