并发编程-线程中断机制

898 阅读4分钟

在前面的线程池系列文章 线程池源码-线程全部关闭了吗 ,提到了线程池 shutdown 过程会使用线程的中断机制去关闭线程,试着问自己一个问题——调用线程的中断方法 interrupt() 就能中断线程吗?

起源

试想一下,你的电脑开着杀毒软件正杀着毒呢,你不耐烦地点击停止按钮企图停止这一过程,本质上来说就是一个线程想终止另外一个线程,这就要求线程间提供一种中断机制,传递中断信号。

而中断的策略也分两种:

  • 暴力型
  • 温柔协商型

暴力型

Thread.stop, Thread.suspend, Thread.resume 这些都是早期中断线程的方法,现在都已经废弃,原因在于它们都太暴力了,容易造成安全性问题。

下面我们举例说明:

public class Main {

    public static void main(String[] args) throws InterruptedException {
        InterruptThread thread = new InterruptThread();
        thread.start();
        Thread.sleep(1000);
        // 暂停线程
        thread.stop();
        // thread.interrupt();
        // 确保线程已经销毁
        while (thread.isAlive()) { }
        // 输出结果
        thread.print();
    }


}

class InterruptThread extends Thread {
    private int x = 0;
    private int y = 0;

    @Override
    public void run() {
        // 这是一个同步原子操作
        synchronized (this) {
            ++x;
            try {
                // 休眠3秒,模拟耗时操作
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ++y;
        }
    }

    public void print() {
        System.out.println("x=" + x + " y=" + y);
    }
}

输出结果:

可以看到我们模拟了一个原子操作,按照预期应该是要输出 x = 1 y = 1 的结果,但是线程在毫无察觉的情况下被中断了,有点像被别人"敲了闷棍",根本没有反应的机会。

温柔协商型

把上面测试代码中的中断策略换成 thread.interrupt();

输出结果:

可以看到,中断操作抛了异常,并且我们能够 catch 住这个异常,进行后续的处理,使得最终输出结果符合预期。

interrupt() 设计理念是『一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止』,是一种比较温柔的做法,它更类似一个标志位。其作用不是中断线程,而是「通知线程应该中断了」,具体到底中断还是继续运行,应该由被通知的线程自己处理。

interrupt() 并不能真正的中断线程,需要被调用的线程自己进行配合才行。也就是说,一个线程如果有被中断的需求,那么就需要这样做:

  1. 在正常运行任务时,经常检查本线程的中断标志位,如果被设置了中断标志就自行停止线程。
  2. 在调用阻塞方法时正确处理InterruptedException异常。(例如:catch异常后就结束线程。)

下面来看一下 Thread 类 interrupt 相关的几个方法。

   // 核心 interrupt 方法
   public void interrupt() {
        // 非本线程,需要检查权限
        if (this != Thread.currentThread()) 
            checkAccess();

        synchronized (blockerLock) {
            Interruptible b = blocker;
            if (b != null) {
                // 设置中断标志位
                interrupt0();    
                // 调用如 I/O 操作定义的中断方法
                b.interrupt(this);   
                return;
            }
        }
        interrupt0();
    }
    // 静态方法,需要注意调用该方法会清除中断状态。
    public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }
    // 这个方法不会清除中断状态,仅仅返回中断状态
    public boolean isInterrupted() {
        return isInterrupted(false);
    }
   // 上面两个方法会调用这个本地方法,参数代表是否清除中断状态
   private native boolean isInterrupted(boolean ClearInterrupted);

首先介绍一下 interrupt() 方法:

  • 非自身线程进行中断操作时,需要进行权限检测

  • 线程处于 sleep, wait, join 状态,是可以响应中断的,会立即抛出 InterruptedException 异常

  • 如果线程处于I/O阻塞状态,将会抛出ClosedByInterruptException(IOException的子类)异常

  • 如果线程在Selector上被阻塞,select方法将立即返回;

  • 如果非以上情况,将直接标记 interrupt 状态;


阻塞情况下中断,抛出异常后线程恢复非中断状态,即 interrupted = false

public class Main {

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new InterruptTask("1"));
        thread.start();
        thread.interrupt();
    }
    
}

class InterruptTask implements Runnable {
    String name;

    public InterruptTask(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            System.out.println("thread has been interrupt!");
        }
        System.out.println("isInterrupted: " + Thread.currentThread().isInterrupted());
        System.out.println("task " + name + " is over");
    }

}

输出结果:


调用Thread.interrupted() 方法后线程恢复非中断状态

class InterruptTask implements Runnable {
    String name;

    public InterruptTask(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        System.out.println("first :" + Thread.interrupted());
        System.out.println("second:" + Thread.interrupted());
        System.out.println("task " + name + " is over");
    }

}

输出结果:

总结

通过本篇文章,我们了解了:

  • 传统的中断策略——暴力型,例如 thread.stop 方法
  • 安全的中断策略,线程提供的 interrupted 操作以及它的中断机制
  • 线程如果有中断的需求,需要自身去响应中断

如果觉得文章对你有帮助,欢迎留言点赞。