线程停止继续_线程不是你想中断就能中断

这是我2021年的第2篇原创文章,原汁原味的技术之路尽在Jerrycodes
  • 为什么不强制停止

  • 如何用 interrupt 停止线程

  • sleep 期间能否感受到中断

  • 停止线程的方式有几种

  • 总结

启动线程需要调用 Thread 类的 start() 方法,并在 run() 方法中定义需要执行的任务。启动一个线程非常简单,但如果想要正确停止它就没那么容易了。

对于实现线程的几种方式,可见我的上一篇文章

实现线程本质上只有一种方式

为什么不强制停止

对于 Java 而言,最正确的停止线程的方式是使用 interrupt。但 interrupt仅仅起到通知被停止线程的作用。而对于被停止的线程而言,它拥有完全的自主权,它既可以选择立即停止,也可以选择一段时间后停止,也可以选择压根不停止。

为什么 Java 不提供强制停止线程的能力呢?事实上,Java 希望程序间能够相互通知、相互协作地管理线程,因为如果不了解对方正在做的工作,贸然强制停止线程就可能会造成一些安全的问题。

比如:线程正在写入一个文件,这时收到终止信号,它就需要根据自身业务判断,是选择立即停止,还是将整个文件写入成功后停止。如果选择立即停止就可能造成数据不完整,不管是中断命令发起者,还是接收者都不希望数据出现问题。

如何用 interrupt 停止线程

while (!Thread.currentThread().isInterrupted() 
&& more work to do) {
    do more work
}

我们一旦调用某个线程的 interrupt() 之后,这个线程的中断标记位就会被设置成 true。每个线程都有这样的标记位,当线程执行时,应该定期检查这个标记位,如果标记位被设置成 true,就说明有程序想终止该线程。

回到源码,可以看到在 while 循环体判断语句中,首先通过

Thread.currentThread().isInterrupt() 

判断线程是否被中断,随后检查是否还有工作要做。&& 逻辑表示只有当两个判断条件同时满足的情况下,才会去执行下面的工作。
public class StopThread implements Runnable {
 
    @Override
    public void run() {
        int count = 0;
        while (!Thread.currentThread().isInterrupted() && count 1000) {
            System.out.println("count = " + count++);
        }
    }
 
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new StopThread());
        thread.start();
        Thread.sleep(5);
        thread.interrupt();
    }
}

在 StopThread 类的 run() 方法中,首先判断线程是否被中断,然后判断 count 值是否小于 1000。

这个线程的工作内容很简单,就是打印 0~999 的数字,每打印一个数字 count 值加 1,可以看到,线程会在每次循环开始之前,检查是否被中断了。接下来在 main 函数中会启动该线程,然后休眠 5 毫秒后立刻中断线程,该线程会检测到中断信号,于是在还没打印完1000个数的时候就会停下来,这种就属于通过 interrupt 正确停止线程的情况。

sleep 期间能否感受到中断

先说结论,可以。

public class StopDuringSleep {
 
    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = () -> {
            int num = 0;
            try {
                while (!Thread.currentThread().isInterrupted() && num <= 1000) {
                    System.out.println(num);
                    num++;
                    Thread.sleep(1000000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
        Thread.sleep(5);
        thread.interrupt();
    }
}

运行后的结果你猜怎么着,程序会抛出异常

38d9a4da9749d0984e9436e8a2037680.png
如果 sleep、wait 等可以让线程进入阻塞的方法使线程休眠了,而处于休眠中的线程被中断,那么线程是可以感受到中断信号的,并且会抛出一个 InterruptedException 异常,同时清除中断信号,将中断标记位设置成 false。这样一来就不用担心长时间休眠中线程感受不到中断了,因为即便线程还在休眠,仍然能够响应中断通知,并抛出异常。

但是这样只能相应一次中断信号了,怎么办?我的业务还没有完成收尾,怎么办?

合理利用好 try/catch

我们在实际开发中不能盲目吞掉中断,如果不在方法签名中声明,也不在 catch 语句块中再次恢复中断,而是在 catch 中不作处理,我们称这种行为是“屏蔽了中断请求”。如果我们盲目地屏蔽了中断请求,会导致中断信号被完全忽略,最终导致线程无法正确停止。

    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
//        此处处理中断异常请求,业务收尾
    }

停止线程的方式有几种

void shutdown;
boolean isShutdown;
boolean isTerminated;
boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;
List shutdownNow;

下面我们就对这些方法逐一展开。

shutdown()

调用 shutdown() 方法之后线程池并不是立刻就被关闭,因为这时线程池中可能还有很多任务正在被执行,或是任务队列中有大量正在等待被执行的任务,调用 shutdown() 方法后线程池会在执行完正在执行的任务和队列中等待的任务后才彻底关闭。但这并不代表 shutdown() 操作是没有任何效果的,调用 shutdown() 方法后如果还有新的任务被提交,线程池则会根据拒绝策略直接拒绝后续新提交的任务。

isShutdown()

它可以返回 true 或者 false 来判断线程池是否已经开始了关闭工作,也就是是否执行了 shutdown 或者 shutdownNow 方法。这里需要注意,如果调用 isShutdown() 方法的返回的结果为 true 并不代表线程池此时已经彻底关闭了,这仅仅代表线程池开始了关闭的流程,也就是说,此时可能线程池中依然有线程在执行任务,队列里也可能有等待被执行的任务。

isTerminated()

这个方法可以检测线程池是否真正“终结”了,这不仅代表线程池已关闭,同时代表线程池中的所有任务都已经都执行完毕了,因为我们刚才说过,调用 shutdown 方法之后,线程池会继续执行里面未完成的任务,不仅包括线程正在执行的任务,还包括正在任务队列中等待的任务。比如此时已经调用了 shutdown 方法,但是有一个线程依然在执行任务,那么此时调用 isShutdown 方法返回的是 true ,而调用 isTerminated 方法返回的便是 false ,因为线程池中还有任务正在在被执行,线程池并没有真正“终结”。直到所有任务都执行完毕了,调用 isTerminated() 方法才会返回 true,这表示线程池已关闭并且线程池内部是空的,所有剩余的任务都执行完毕了。

awaitTermination()

第四个方法叫作 awaitTermination(),它本身并不是用来关闭线程池的,而是主要用来判断线程池状态的。比如我们给 awaitTermination 方法传入的参数是 10 秒,那么它就会陷入 10 秒钟的等待,直到发生以下三种情况之一:

  1. 等待期间(包括进入等待状态之前)线程池已关闭并且所有已提交的任务(包括正在执行的和队列中等待的都执行完毕,相当于线程池已经“终结”了,方法便会返回true

  2. 等待超时时间到后,第一种线程池“终结”的情况始终未发生,方法返回 false

  3. 等待期间线程被中断,方法会抛出 Interruptedexception异常

等待期间(包括进入等待状态之前)线程池已关闭并且所有已提交的任务(包括正在执行的和队列中等待的)都执行完毕,相当于线程池已经“终结”了,方法便会返回 true;

等待超时时间到后,第一种线程池“终结”的情况始终未发生,方法返回 false;等待期间线程被中断,方法会抛出 InterruptedException 异常。

shutdownNow()

最后一个方法是 shutdownNow(),也是 5 种方法里功能最强大的,它与第一种 shutdown 方法不同之处在于名字中多了一个单词 Now,也就是表示立刻关闭的意思。在执行 shutdownNow 方法之后,首先会给所有线程池中的线程发送 interrupt 中断信号,尝试中断这些任务的执行,然后会将任务队列中正在等待的所有任务转移到一个 List 中并返回,我们可以根据返回的任务 List 来进行一些补救的操作,例如记录在案并在后期重试。

public List shutdownNow() { 
    List tasks;final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();try { 
        checkShutdownAccess();
        advanceRunState(STOP);
        interruptWorkers();
        tasks = drainQueue();
    } finally { 
        mainLock.unlock();
    } 
    tryTerminate();return tasks;
 }

源码中有一行 interruptWorkers() 代码,这行代码会让每一个已经启动的线程都中断,这样线程就可以在执行任务期间检测到中断信号并进行相应的处理,提前结束任务。这里需要注意的是,由于 Java 中不推荐强行停止线程的机制的限制,即便我们调用了 shutdownNow 方法,如果被中断的线程对于中断信号不理不睬,那么依然有可能导致任务不会停止。

总结

中断和关闭线程的方式五花八门,看起来很相似,其实里头大有门道。处理不好,可是会导致程序崩溃的。

码到这里,何不来个在看?

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

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

相关文章

倒序查10条数据_10 | 怎么给字符串字段加索引?

现在&#xff0c;几乎所有的系统都支持邮箱登录&#xff0c;如何在邮箱这样的字段上建立合理的索引&#xff0c;是我们今天要讨论的问题。假设&#xff0c;你现在维护一个支持邮箱登录的系统&#xff0c;用户表是这么定义的&#xff1a;mysql> create table SUser( ID bigin…

保留小数点后三位_【Meta分析】Stata制作森林图时,如何保留三位小数?

系统评价/Meta分析指全面收集所有相关研究并逐个进行严格评价和分析&#xff0c;再用定性或定量合成的方法对资料进行处理得出综合结论的研究方法。在指导学员的过程中发现初学者在学习过程中常常会碰到许多共性问题&#xff0c;本公众号特此开设专栏解答&#xff0c;希望能够和…

android自动计步_Android计步模块实例代码(类似微信运动)

最近在项目中研究计步模块&#xff0c;每天0点开始记录当天的步数&#xff0c;类似微信运动。碰到了不少坑今天有时间整理出来给大家看看。做之前在google、baidu、github上搜了个遍没找到好的&#xff0c;大多数都是需要在后台存活&#xff0c;需要后台Service。对于现在的各大…

python井字棋ai_实现AI下井字棋的alpha-beta剪枝算法(python实现)

代码参考自中国大学mooc上人工智能与信息社会陈斌老师的算法&#xff0c;我在原来的基础上增加了玩家输入的异常捕获 AlphaBeta剪枝算法是对Minimax方法的优化&#xff0c;能够极大提高搜索树的效率&#xff0c;如果对这个算法感兴趣的可以去参考相关资料。 当正确理解AlphaBet…

Redis小计(2)

目录 1.exists命令 2.del命令 3.expire/pexpire命令 4.ttl命令 5.redis对于key过期的删除策略 1.exists命令 exists X1 X2 X3 X4&#xff1a;返回四个key存在的个数。 2.del命令 del X1 X2&#xff1a;删除key。 3.expire/pexpire命令 给key设置超时时间。 expire key…

unity 彩带粒子_iOS动画开发----粒子系统---彩带效果

参考博文地址:http://my.oschina.net/u/2340880/blog/485095?fromerrbgjLq4Mw一、粒子发射器iOS中的粒子效果有两部分组成&#xff0c;一部分为发射器&#xff0c;设置例子发射的宏观属性&#xff0c;另一部分是粒子单元&#xff0c;用于设置相应的粒子属性。粒子发射器是基于…

一秒执行一次_《一秒钟》:一贯的粗旷式抓大放小,张艺谋的自命题作业总是要观众自己再做一遍...

还有不变的永远在奔跑的大棉裤花棉袄的圆脸妮子&#xff0c;这是导演张艺谋最新作品《一秒钟》的最直接观感。张艺谋是个善于从普世情怀处挖掘题材的导演。之前诸多现实题材类型作品&#xff0c;诸如讲父子和解的《千里走单骑》、夫妻爱情的《归来》以及《我的父亲母亲》&#…

latex 作者加小标_Latex 写期刊论文的小技巧

在不同文字处理系统(如 MiKTeX, TeX Live, CTeX, cwTex) 或 不同整合开发环境 ( 如Texstudio, WinEdt, TeXstudio, TeXmaker) 中&#xff0c;我用了 Miktex Texstudio 的常用组合 (win10环境中)。1: 先MiKTeX&#xff0c;后Texstudio ;2&#xff1a; 安装包(packages);3&#…

unity 畸变_unity3d 几种镜头畸变

1.Fisheye distortion 鱼眼镜头解释来自百度百科&#xff1a;鱼眼镜头是一种焦距为16mm或更短的并且视角接近或等于180。 它是一种极端的广角镜头&#xff0c;“鱼眼镜头”是它的俗称。为使镜头达到最大的摄影视角&#xff0c;这种摄影镜头的前镜片直径很短且呈抛物状向镜头前…

restfull加签_SpringBoot RestFull API签名

一、需求如下对指定的API路径进行签名认证&#xff0c;对于没有指定的无需认证&#xff0c;认证具体到方法。二、查阅资料与开发1.了解JWT&#xff0c;实际上用的开源jjwt2.编写自定义注解3.编写拦截器&#xff0c;主要是拦截特定的url进行签名验证&#xff0c;这里解析请求的h…

mysql 5.5.18下载_MySQL5.7.18下载和安装过程图文详解

MySql下载1、打开官网找到下载路口&#xff0c;这里直接给出下载的地址2、选择64位版本3、直接下载MySql5.7.18.1安装过程1 、运行安装软件&#xff0c;接受协议2、选择默认安装3、下一步到检查环境界面&#xff0c;点击“Execute”执行检查 (可以后面单独下载插件安装)&…

mysql找不到performance_Mysql安装完毕运行时没有mysql和performance_schema数据库_MySQL

Mysql问题 ERROR 1045 (28000): Access denied for user ‘root’’localhost’ (using password: YES)Mysql安装完毕运行时没有 mysql 和 performance_schema 数据库问题一&#xff1a;之前卸载未卸载干净问题二&#xff1a;没有管理员权限进入问题三&#xff1a;登录时&#…

mysql latid1_mysql触发器的实战经验

1 引言Mysql的触发器和存储过程一样&#xff0c;都是嵌入到mysql的一段程序。触发器是mysql5新增的功能&#xff0c;目前线上凤巢系统、北斗系统以及哥伦布系统使用的数据库均是mysql5.0.45版本&#xff0c;很多程序比如fc-star管理端&#xff0c;sfrd(das)&#xff0c;dorad…

mysql数据库sql注入原理_SQL注入原理解析以及举例1

sql注入是指web应用程序对用户输入数据的合法性没有判断&#xff0c;导致攻击者可以构造不同的sql语句来实现对数据库的操作。sql注入漏洞产生满足条件&#xff1a;1&#xff1b;用户能够控制数据的输入。2&#xff1b;原本需要执行的代码&#xff0c;拼接了用户的输入。举例&a…

mysql存储map数据结构_map数据结构

Go map实现原理 - 恋恋美食的个人空间 - OSCHINA - 中文开源技术交流社区 https://my.oschina.net/renhc/blog/2208417// A header for a Go map.type hmap struct {// Note: the format of the hmap is also encoded in cmd/compile/internal/gc/reflect.go.// Make sure this…

四因素三水平正交表_做论文要用正交表?我打包送给你

正交试验目前在国内的应用量仍然是比较高的&#xff0c;许多高校毕业生喜欢利用正交试验来获取研究数据&#xff0c;最终完成毕业论文的撰写或者期刊投稿。正交试验方案的设计&#xff0c;必然要用到(标准)正交表。那么大家都是从哪里获取正交表的呢&#xff1f;小兵给这方面的…

plsql视图添加表字段_Oracle-单表多字段查询(不使用*)

环境&#xff1a;Oracle 11g&#xff0c;plsql 14目的&#xff1a;不使用*,查询拥有上百个字段的表的所有字段。懒人大法&#xff1a;在文章末尾。sql实现逻辑&#xff1a;1、首先建一张100个字段以上的表&#xff0c;通过excel的方式将表建好后直接复制粘贴到plsql的建表界面。…

mysql 编译安装与rpm安装的区别_编译安装与RPM安装的区别

建议在安装线上的生产服务器软件包时都用源码安装&#xff0c;这是因为源码安装可以自行调整编译参数&#xff0c;最大化地定制安装结果。这里以MySQL 5线上环境的编译安装来说明之&#xff0c;其编译参数如下所示&#xff1a;./configure-prefix/usr/local/mysql -without-deb…

python字符串变量s的值是python网络爬虫_【Python爬虫作业】-字符串

一、定义字符串变量1.请定义三个字符串a,b,c值分别为 I,like, python2.请将上面三个变量合并输出I like pythonaIblikecpythonprint(a)print(b)print(c)print(a,b,c)二、定义一个变量 s sdghHhf 1.请先将变量s的空白符去掉 赋值给新变量s1 打印输出2.请分别将s1变为全部大写(命…

lableimg闪退_CV学习笔记(二十五):数据集标注与制作

最近在做一些数据标注的工作&#xff0c;虽然标注数据比较枯燥&#xff0c;但这也是每个做算法的工程师升级打怪的必由之路。使用一些合适的工具往往可以事半功倍&#xff0c;效率UP。一&#xff1a;数据标注流程二&#xff1a;数据处理的一些小代码1&#xff1a;重命名当得到这…