一文搞懂Java线程中断协商机制,如何优雅中断一个正在运行的线程?

news2024/12/23 10:53:49

文章目录

  • 一、中断机制概述
    • 1、中断API
    • 2、什么是中断机制
    • 3、如何理解中断机制
    • 4、三大中断方法源码详解
      • (1)interrupt()
      • (2)interrupted()
      • (3)isInterrupted()
      • (4)注意!不同jdk版本中源码的异同
  • 二、动手实现线程的中断
    • 1、通过一个volatile变量实现
    • 2、通过AtomicBoolean实现
    • 3、使用中断API
    • 4、证明:调用interrupt方法不会强制中断线程
    • 5、中断sleep、wait中的线程

一、中断机制概述

1、中断API

在Thread类中,有三个关于中断的核心方法,这就是Java中断的核心方法:
在这里插入图片描述

2、什么是中断机制

一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止,自己来决定自己的命运。
所以,Thread.stop()、Thread.suspend、Thread.resume()等关于强制线程停止的方法都已经废弃了。

在Java中,没有办法立即停止一个线程,然而停止线程本身这个功能是很重要的,比如说取消一个耗时的操作。
因此,Java提供了一种用于停止线程的协商机制:中断,也即中断标识协商机制

Java的中断只是一种协商机制,Java并没有给中断增加任何语法,中断的过程完全需要程序员自己来实现
若要中断一个线程,我们需要手动调用该线程的interrupt方法,该方法也仅仅是将线程对象的中断标识设为true;接着我们需要自己写代码不断地在程序每一个关键点检测当前线程的标识位,如果为true,表示别的线程请求中断这条线程,此时究竟该做些什么,需要我们自己写代码实现。

每个线程对象中都有一个中断标识位,用于标识线程是否被中断;该标识位为true表示中断,为false表示未中断;通过调用线程对象的interrupt()方法将该线程的中断标识位设为true,可以在别的线程中调用,也可以在自己的线程中调用。

3、如何理解中断机制

当一个耗时操作被发起时,也许是请求方等不及了,或者又不需要继续执行下去了,我们此时想要中断这个线程,此时就可以使用Java的中断机制。

比如说一个无烟商场,有个顾客正在一个角落抽烟,商场管理员能做的只能劝阻顾客:“请不要在此处抽烟,吸烟请到无烟区”。劝阻之后,是否继续吸烟完全由顾客来决定,顾客收到劝阻这个事件之后,同样可以做的不单单是“吸烟、不吸烟”这两个动作,也可以做其他任意的动作。

但是,中断机制实现起来复杂,应用场景很窄,实际情况应用的非常少。

4、三大中断方法源码详解

(1)interrupt()

实例方法:仅仅是将线程的中断标识设置为true,发起一个协商而不会立即停止线程。

除非当前线程正在中断自身(这是始终允许checkAccess的),否则将调用此线程的方法,这可能会导致抛出 。SecurityException

如果此线程在调用wait()类的 、 或方法或此类的 Object 、 join(long)join(long, int)sleep(long)wait(long)或wait(long, int)sleep(long, int)方法时join()被阻塞,则其中断状态将被清除,并且将收到 InterruptedException

如果此线程在 的 InterruptibleChannel I/O 操作中被阻塞,则通道将关闭,线程的中断状态将被设置,并且线程将收到 java.nio.channels.ClosedByInterruptException。

如果此线程在 中 java.nio.channels.Selector 被阻塞,则将设置线程的中断状态,并且它将立即从选择操作返回,可能具有非零值,就像调用选择器的方法 wakeup 一样。

如果上述条件均不成立,则将设置此线程的中断状态。

中断不活动的线程不需要有任何效果

public void interrupt() {
    if (this != Thread.currentThread()) {
        checkAccess(); // 安全检查

        // thread may be blocked in an I/O operation
        synchronized (blockerLock) {
            Interruptible b = blocker;
            if (b != null) {
                interrupted = true; // 线程中断标识位设置为true
                interrupt0();  // inform VM of interrupt
                b.interrupt(this);
                return;
            }
        }
    }
    interrupted = true;
    // inform VM of interrupt
    interrupt0();
}

private native void interrupt0();

(2)interrupted()

静态方法:interrupted(),判断线程是否被中断,并清除当前中断状态。

换句话说,如果要连续调用此方法两次,则第二次调用将返回 false(除非在第一个调用清除其中断状态之后,在第二个调用检查它之前,当前线程再次中断)。

这个方法做了两件事:
1.返回当前线程的中断状态,测试当前线程是否已被中断。
2.将当前线程的中断状态清零并重新设为false,清除线程的中断状态。

public static boolean interrupted() {
    Thread t = currentThread();
    boolean interrupted = t.interrupted;
    // We may have been interrupted the moment after we read the field,
    // so only clear the field if we saw that it was set and will return
    // true; otherwise we could lose an interrupt.
    if (interrupted) {
        t.interrupted = false;
        clearInterruptEvent();
    }
    return interrupted;
}

private static native void clearInterruptEvent();

(3)isInterrupted()

实例方法:isInterrupted()

判断当前线程是否被中断(通过检查中断标识位)

public boolean isInterrupted() {
    return interrupted;
}

(4)注意!不同jdk版本中源码的异同

以上源码是jdk17版本,高版本jdk中,引入了一个内部变量interrupted作为线程中断标识,而jdk8完全是调用底层native方法来判断的,这无疑在性能上会有较大的差距。

我们看一下jdk8的源码:

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

    synchronized (blockerLock) {
        Interruptible b = blocker;
        if (b != null) {
            interrupt0();           // Just to set the interrupt flag
            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);

二、动手实现线程的中断

1、通过一个volatile变量实现

public class InterruptDemo
{
    static volatile boolean isStop = false;

    private static void main()
    {
        new Thread(() -> {
            while (true)
            {
                if(isStop)
                {
                    System.out.println(Thread.currentThread().getName()+"\t isStop被修改为true,程序停止");
                    break;
                }
                System.out.println("t1 -----hello volatile");
            }
        },"t1").start();

        //暂停毫秒
        try { TimeUnit.MILLISECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }

        new Thread(() -> {
            isStop = true;
        },"t2").start();
    }
}

使用volatile可以保证变量的内存可见性,t1线程不断地在关键步骤处检测中断标识是否为true,当另一个线程将中断标识设为true时,t1线程检测到,并终止程序。

2、通过AtomicBoolean实现

public class InterruptDemo
{
    static AtomicBoolean atomicBoolean = new AtomicBoolean(false);

    private static void main()
    {
        new Thread(() -> {
            while (true)
            {
                if(atomicBoolean.get())
                {
                    System.out.println(Thread.currentThread().getName()+"\t atomicBoolean被修改为true,程序停止");
                    break;
                }
                System.out.println("t1 -----hello atomicBoolean");
            }
        },"t1").start();

        //暂停毫秒
        try { TimeUnit.MILLISECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }

        new Thread(() -> {
            atomicBoolean.set(true);
        },"t2").start();
    }
}

同样的,使用AtomicBoolean 原子类,可以实现通过一个变量充当线程中断标识。

3、使用中断API

public class InterruptDemo
{
    public static void main(String[] args)
    {
        Thread t1 = new Thread(() -> {
            while (true)
            {
                if(Thread.currentThread().isInterrupted())
                {
                    System.out.println(Thread.currentThread().getName()+"\t isInterrupted()被修改为true,程序停止");
                    break;
                }
                System.out.println("t1 -----hello interrupt api");
            }
        }, "t1");
        t1.start();

        System.out.println("-----t1的默认中断标志位:"+t1.isInterrupted());

        //暂停毫秒
        try { TimeUnit.MILLISECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }

        //t2向t1发出协商,将t1的中断标志位设为true希望t1停下来
        new Thread(() -> {
            t1.interrupt();
        },"t2").start();
        //t1.interrupt();

    }
}

此处我们使用线程中断的内置方法,实现了中断。

4、证明:调用interrupt方法不会强制中断线程

public class InterruptDemo2
{
    public static void main(String[] args)
    {
        //实例方法interrupt()仅仅是设置线程的中断状态位设置为true,不会停止线程
        Thread t1 = new Thread(() -> {
            for (int i = 1; i <=300; i++)
            {
                System.out.println("-----: "+i);
            }
            System.out.println("t1线程调用interrupt()后的的中断标识02:"+Thread.currentThread().isInterrupted());
        }, "t1");
        t1.start();

        System.out.println("t1线程默认的中断标识:"+t1.isInterrupted());//false

        //暂停毫秒
        try { TimeUnit.MILLISECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
        t1.interrupt();//true
        System.out.println("t1线程调用interrupt()后的的中断标识01:"+t1.isInterrupted());//true

        try { TimeUnit.MILLISECONDS.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println("t1线程调用interrupt()后的的中断标识03:"+t1.isInterrupted());//????---false中断不活动的线程不会产生任何影响。
    }
}

以上程序可以看出,我们调用线程的interrupt方法,将线程的中断标识位设为true,但是并不会立即停止该线程,因为该线程中并没有编写关于中断处理的逻辑。

5、中断sleep、wait中的线程

public class InterruptDemo3
{
    public static void main(String[] args)
    {
        Thread t1 = new Thread(() -> {
            while (true)
            {
                if(Thread.currentThread().isInterrupted())
                {
                    System.out.println(Thread.currentThread().getName()+"\t " +
                            "中断标志位:"+Thread.currentThread().isInterrupted()+" 程序停止");
                    break;
                }

                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    //Thread.currentThread().interrupt();//为什么要在异常处,再调用一次??
                    e.printStackTrace();
                }

                System.out.println("-----hello InterruptDemo3");
            }
        }, "t1");
        t1.start();

        //暂停几秒钟线程
        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

        new Thread(() -> t1.interrupt(),"t2").start();
    }
}

我们发现,在sleep中的线程,被调用了interrupt方法之后,会抛出InterruptedException 中断休眠,此时中断状态会被清除,并将中断标识位设置为false。

/**
使当前正在执行的线程在指定的毫秒数内休眠(暂时停止执行),具体取决于系统计时器和计划程序的精度和准确性。线程不会失去任何监视器的所有权。
参数:
millis – 睡眠时间长度(以毫秒为单位)
抛出:
IllegalArgumentException – 如果 的值 millis 为负数
InterruptedException – 如果任何线程中断了当前线程。引发此异常时,将清除当前线程的 中断状态 。
*/
public static native void sleep(long millis) throws InterruptedException;

在catch块中,需要再次给中断标志位设置为true,需要手动再次将线程中断标识设为true,停止程序。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1063451.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Electron笔记

基础环境搭建 官网:https://www.electronjs.org/zh/ 这一套笔记根据这套视频而写的 创建项目 方式一: 官网点击GitHub往下拉找到快速入门就能看到下面这几个命令了 git clone https://github.com/electron/electron-quick-start //克隆项目 cd electron-quick-start //…

阿里云服务器e系列是共享型?什么意思?

阿里云服务器经济型e实例是共享型云服务器&#xff0c;共享型实例采用非绑定CPU调度模式。每个vCPU会被随机分配到任何空闲CPU超线程上&#xff0c;不同实例vCPU会争抢物理CPU资源&#xff0c;并导致高负载时计算性能波动不稳定&#xff0c;有可用性SLA保证&#xff0c;但无性能…

【网络安全 --- 工具安装】VMware 16.0 详细安装过程(提供资源)

一&#xff0c;VMware下载地址&#xff1a; 百度网盘链接链接&#xff1a;百度网盘 请输入提取码百度网盘为您提供文件的网络备份、同步和分享服务。空间大、速度快、安全稳固&#xff0c;支持教育网加速&#xff0c;支持手机端。注册使用百度网盘即可享受免费存储空间https:/…

【开发篇】十六、SpringBoot整合JavaMail实现发邮件

文章目录 0、相关协议1、SpringBoot整合JavaMail2、发送简单邮件3、发送复杂邮件 0、相关协议 SMTP&#xff08;Simple Mail Transfer Protocol&#xff09;&#xff1a;简单邮件传输协议&#xff0c;用于发送电子邮件的传输协议POP3&#xff08;Post Office Protocol - Versi…

IPv6协议报文头

IPv6协议概述 IPv6&#xff08;Internet Protocol Version 6&#xff09;是网络层协议的第二代标准协议&#xff0c;也被成为IPng&#xff08;IP Next Generation&#xff09;。它是Internet工程任务组IETF&#xff08;Internet Engineering Task Force&#xff09;设计的一套…

ros2移植Apollo和autoware规控算法可跑工程

工程详细介绍请看&#xff1a; 自动驾驶路径规划控制ros移植Apollo和autoware规控算法可跑工程&#xff08;适合入门学习&#xff0c;科研和实战&#xff09; ros2的工程版本说明 之所以增加ros2版本&#xff0c;是因为想增加代码的工程应用性&#xff0c;其实对于科研来说并…

计算机专业毕业设计项目推荐11-博客项目(Go+Vue+Mysql)

博客项目&#xff08;GoVueMysql&#xff09; **介绍****系统总体开发情况-功能模块****各部分模块实现** 介绍 本系列(后期可能博主会统一为专栏)博文献给即将毕业的计算机专业同学们,因为博主自身本科和硕士也是科班出生,所以也比较了解计算机专业的毕业设计流程以及模式&am…

正点原子嵌入式linux驱动开发——U-boot启动流程详解

在上一篇笔记中详细分析了uboot的顶层Makefile&#xff0c;理清了uboot的编译流程。本章来详细的分析一下uboot的启动流程&#xff0c;理清uboot是如何启动的。通过对uboot启动流程的梳理&#xff0c;可以掌握一些外设是在哪里被初始化的&#xff0c;这样当需要修改这些外设驱动…

14885-2010 固定资产分类与代码 思维导图

声明 本文是学习GB-T 14885-2010 固定资产分类与代码…pdf而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 1 范围 本标准规定了固定资产的分类、代码及计量单位。 本标准适用于固定资产管理、清查、登记、统计等工作。 2 术语和定义 下列术语和定义…

由[哈希/散列]模拟实现[unordered_map/unordered_set] (手撕迭代器)

文章目录 1.迭代器分析2.细节处理3.完整代码3.1HashTable.h3.2unordered_set.h3.3unordered_map.h3.4Test.cpp 1.迭代器分析 2.细节处理 以下两篇文章均为笔者的呕心沥血想要搞懂本篇文章的uu请自行查阅 哈希/散列的细节实现 哈希/散列–哈希表[思想到结构][修订版] 手撕迭代器…

硬件知识:U盘相关知识介绍,值得收藏

目录 什么是U盘&#xff1f; U盘根据结构有哪几种&#xff1f; 根据U盘的存储介质、外形、功能分类有哪几种&#xff1f; 什么是U盘&#xff1f; U盘&#xff0c;全称为USB闪存盘&#xff0c;是一种以闪存芯片作为数据存储介质的移动存储设备。U盘的历史可以追溯到1998年&am…

vue3 中使用 echarts 图表——准备篇

我们常常在项目中使用图表来表示数据&#xff0c;而目前最常用的图标就是echarts&#xff0c;接下来我们就开始学习在vue中使用echarts图标。 一、准备一个vue项目&#xff08;一般通过vite来构建&#xff0c;而不是vue-cli&#xff09; 1.找到打开vite官网 2. 运行创建命令 …

MySQL进阶-存储引擎

目录 1.MySQL体系结构 体系结构图 各层的作用 2.存储引擎简介 2.1查看当前表的存储引擎 2.2 查询mysql支持的存储引擎 2.3 InnoDB简介 2.4 MyISAM简介 2.5 Memory简介 3.存储引擎的选择 1.MySQL体系结构 mysql体系结构主要有四层结构&#xff0c;从上到下依次是&#…

Spring Cloud zuul扩展能力设计和心得

前言 实际上Spring Cloud已经废弃zuul了&#xff0c;改用gateway&#xff0c;但是webflux的技术并没在实际项目大规模普及&#xff0c;还有很多servlet NIO的应用&#xff0c;所以zuul还是很有必要改造的&#xff0c;实测zuul调优&#xff08;调节转发的连接池&#xff09;跟g…

【算法挨揍日记】day11——852. 山脉数组的峰顶索引、162. 寻找峰值

852. 山脉数组的峰顶索引 852. 山脉数组的峰顶索引 题目描述&#xff1a; 符合下列属性的数组 arr 称为 山脉数组 &#xff1a; arr.length > 3存在 i&#xff08;0 < i < arr.length - 1&#xff09;使得&#xff1a; arr[0] < arr[1] < ... arr[i-1] < …

数据结构:二叉树(超详解析)

目录​​​​​​​ 1.树概念及结构 1.1树的概念 1.2树的相关概念 1.3树的表示 1.3.1孩子兄弟表示法&#xff1a; 1.3.2双亲表示法&#xff1a;只存储双亲的下标或指针 两节点不在同一树上&#xff1a; 2.二叉树概念及结构 2.1.概念 2.2.特殊的二叉树&#xff1a; 2…

掌握交易时机!

“您是否知道您选择购买和出售加密货币的时间会产生很大的影响&#xff1f;当然&#xff0c;大多数交易者都知道高价卖出和低价买入的基本知识。然而&#xff0c;在选择交易加密货币的最佳时机时&#xff0c;还需要考虑许多其他小细节。加密货币市场分析表明&#xff0c;一天中…

【MyBatis-Plus】快速精通Mybatis-plus框架—核心功能

刚才的案例中都是以id为条件的简单CRUD&#xff0c;一些复杂条件的SQL语句就要用到一些更高级的功能了。 1.条件构造器 除了新增以外&#xff0c;修改、删除、查询的SQL语句都需要指定where条件。因此BaseMapper中提供的相关方法除了以id作为where条件以外&#xff0c;还支持…

ES 关于 remote_cluster 的一记小坑

最近有小伙伴找到我们说 Kibana 上添加不了 Remote Cluster&#xff0c;填完信息点 Save 直接跳回原界面了。具体页面&#xff0c;就和没添加前一样。 我们和小伙伴虽然隔着网线但还是进行了深入、详细的交流&#xff0c;梳理出来了如下信息&#xff1a; 两个集群&#xff1a;…

Java常见API---split()

package daysreplace;public class SplitTest {public static void main(String[] args) {String str"武汉市|孝感市|长沙市|北京市|上海市";String[] array str.split("\\|");System.out.println(array[0]);System.out.println(array[1]);System.out.pri…