二十九.线程的中断详解

前言

JAVA中的线程,有一个状态叫做中断状态,用于标记线程是否被中断过。通过对线程中断状态的判断,可以实现一些例如优雅终止线程唤醒线程等功能。

Thread类中有interrupt()interrupted()isInterrupted()方法与线程的中断有关,本篇文章将对这些方法的具体作用进行详细解释。在文章的最后,还会通过一个典型例子,演示如何通过线程的中断状态来优雅的终止线程。

正文

一. Thread#interrupt方法详解

Thread类提供了interrupt()方法来中断线程,哪个线程对象的interrupt()方法被调用,那么这个线程就会被中断。关于interrupt()方法,有如下注意点。

  1. interrupt()方法是成员方法
  2. 通常不能在线程内部调用线程的interrupt()方法来中断线程自己;
  3. 如果线程正阻塞在Object#wait()Object#wait(long)Object#wait(long, int)Thread#join()Thread#join(long)Thread#join(long, int)Thread#sleep(long)Thread#sleep(long, int)方法上,然后被interrupt()方法中断,此时中断状态会被重置为falsefalse表示未被中断),并且被中断的线程会收到中断异常InterruptedException

上面最重要的就是第3点,第3点中罗列出来的方法在使用时都需要try-catch中断异常InterruptedException,但是在这个中断异常被抛出前,线程的中断状态会被重置为false,这就造成一种现象:因为被中断而从阻塞方法中唤醒的线程的中断状态是false。这一点请切记。

二. Thread#interrupted方法详解

Thread类提供了静态方法interrupted()来得到调用该方法的线程的中断状态。关于interrupted()方法,有如下注意点。

  1. interrupted()方法是静态方法
  2. 哪个线程调用interrupted()方法,就会返回这个线程的中断状态,然后线程的中断状态会重置为false

interrupted()方法在调用后,调用线程的中断状态会重置为false,这一点请切记。

三. Thread#isInterrupted()方法详解

Thread类提供了isInterrupted()方法来得到线程的中断状态,哪个线程对象的isInterrupted()方法被调用,就会返回这个线程的中断状态。关于isInterrupted()方法,有如下注意点。

  1. isInterrupted()方法是成员方法
  2. 哪个线程对象的isInterrupted()方法被调用,就会返回这个线程的中断状态,并且中断状态不会重置为false

isInterrupted()方法和interrupted()方法都能获取到线程的中断状态,不同点在于:isInterrupted()方法获取到线程的中断状态后,线程的中断状态不会重置为false,而interrupted()方法会。

四. 优雅的终止线程

首先明确一点:一个线程的终止,不能由其它线程来强行终止。这也就是为什么Thread#stop()Thread#suspend等方法会被废弃。

一个优雅终止线程的简单思路就是:在线程执行的任务中不断的判断一个标志位,当标志位满足某种条件时,任务结束运行,从而线程优雅终止。如下是一个简单示例。

public class InterruptedTest {

    @Test
    public void 优雅终止线程的简单示例() {
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 5,
                60, TimeUnit.SECONDS, new LinkedBlockingQueue<>());

        // 执行任务
        for (int i = 0; i < 5; i++) {
            threadPool.execute(() -> {
                Thread thread = Thread.currentThread();
                while (!thread.isInterrupted()) {
                    // empty run
                }
                System.out.println(thread.getName() + " 任务执行完毕");
            });
        }

        // 主线程等待2秒,等待任务充分执行
        LockSupport.parkNanos(1000 * 1000 * 1000 * 2L);

        // shutdownNow线程池
        threadPool.shutdownNow();

        // 主线程等待1秒,等待任务关闭
        LockSupport.parkNanos(1000 * 1000 * 1000);
    }

}

上述示例中,每个线程执行的任务中会一直判断当前线程的中断状态,如果中断状态为true(表示线程被中断了),那么就退出while循环,从而任务执行完毕,最终线程被优雅终止。运行测试程序,打印如下。

在这里插入图片描述

运行结果表明线程确实被终止了。上述示例中线程执行的任务实际上是响应中断的,因此在线程被调用interrupt()方法时,能够响应中断,从而结束任务的执行,最终线程终止。再看如下一个例子,是一个看起来响应中断,却实际不响应中断的典型错误案例。

public class InterruptedTest {

    @Test
    public void 线程池中的任务不响应中断的典型案例() {
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 5,
                60, TimeUnit.SECONDS, new LinkedBlockingQueue<>());

        // 执行任务
        for (int i = 0; i < 5; i++) {
            threadPool.execute(() -> {
                Thread thread = Thread.currentThread();
                while (!thread.isInterrupted()) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        System.out.println(thread.getName() + " 被中断了");
                    }
                }
                System.out.println(thread.getName() + " 任务执行完毕");
            });
        }

        // 主线程等待2秒,等待任务充分执行
        LockSupport.parkNanos(1000 * 1000 * 1000 * 2L);

        // shutdownNow线程池
        threadPool.shutdownNow();

        // 主线程等待1秒,等待任务关闭
        LockSupport.parkNanos(1000 * 1000 * 1000);
    }

}

上述示例中,任务大部分时间是阻塞在Thread#sleep(long)方法上的,但其实Thread#sleep(long)方法是响应中断的,所以上述示例是希望线程被中断时,线程从Thread#sleep(long)方法返回,然后判断线程的中断状态,然后结束任务的运行,最终线程终止。那么运行测试程序,看一下运行结果。

在这里插入图片描述

可见任务没有结束运行,故线程也并没有被终止。原因就是第一节中提到的,线程阻塞在Thread#sleep(long)方法上时如果被中断,那么中断状态会被重置为false然后抛出中断异常InterruptedException,那么上述示例中,任务在判断线程的中断状态时,中断状态会恒为false,故任务永远不会退出。改进如下。

public class InterruptedTest {

    @Test
    public void 线程池中的任务不响应中断的典型案例改进() {
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 5,
                60, TimeUnit.SECONDS, new LinkedBlockingQueue<>());

        // 执行任务
        for (int i = 0; i < 5; i++) {
            threadPool.execute(() -> {
                Thread thread = Thread.currentThread();
                while (!thread.isInterrupted()) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        // 重新将中断状态置为true
                        thread.interrupt();
                        System.out.println(thread.getName() + " 被中断了");
                    }
                }
                System.out.println(thread.getName() + " 任务执行完毕");
            });
        }

        // 主线程等待2秒,等待任务充分执行
        LockSupport.parkNanos(1000 * 1000 * 1000 * 2L);

        // shutdownNow线程池
        threadPool.shutdownNow();

        // 主线程等待1秒,等待任务关闭
        LockSupport.parkNanos(1000 * 1000 * 1000);
    }

}

改进思路就是在任务中捕获到InterruptedException后,再调用一次当前线程对象的interrupt()方法,来将当前线程的中断状态重新置为true,从而任务可以顺利结束运行,最终线程终止。运行测试程序,结果如下。

在这里插入图片描述

可见任务是顺利执行完毕,从而线程完成了优雅终止。

总结

关于JAVA中线程的中断,总结如下。

  1. Thread类提供了interrupt()成员方法来中断线程,哪个线程对象的interrupt()方法被调用,那么这个线程被中断,中断状态会被置为true。但是如果线程是阻塞在Object#waitThread#sleep等方法上时被调用interrupt()方法来中断,那么中断状态会被重置为false,然后抛出中断异常InterruptedException
  2. Thread类提供了interrupted()静态方法来获取线程的中断状态,哪个线程调用interrupted()方法,就返回这个线程的中断状态,同时也重置这个线程的中断状态为false
  3. Thread类提供了isInterrupted()成员方法来获取线程的中断状态,哪个线程对象的isInterrupted()方法被调用,那么就返回这个线程的中断状态,并且不会重置这个线程的中断状态为false
  4. 线程的优雅终止需要线程执行的任务能够根据某种条件来结束运行,从而终止线程。一个简单的思路是在线程执行的任务中不断的判断一个标志位,当标志位满足某种条件时,任务结束运行,从而线程优雅终止
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

樱花祭的约定

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值