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

这是我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;希望能够和…

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…

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

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

unity 畸变_unity3d 几种镜头畸变

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

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

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

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的建表界面。…

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

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

java线程堆栈_深入JVM剖析Java的线程堆栈

在这篇文章里我将教会你如何分析JVM的线程堆栈以及如何从堆栈信息中找出问题的根因。在我看来线程堆栈分析技术是Java EE产品支持工程师所必须掌握的一门技术。在线程堆栈中存储的信息&#xff0c;通常远超出你的想象&#xff0c;我们可以在工作中善加利用这些信息。我的目标是…

java文件 linux_Linux执行Java文件

最近学习shell脚本&#xff0c;写个简单java类让linux去执行java类没别的东西&#xff0c;就引了一个fastjson的jar&#xff0c;写了个main方法 序列化一个User对象 打印package com.lws.demo;import java.util.Date;import com.alibaba.fastjson.JSONObject;import com.lws.mo…

java 刽子手游戏_java基础(九):容器

集合的引入List (ArrayList LinkedList)Set (HashSet LinkedHashSet TreeSet )Map (HashMap LinkedHashMap TreeMap)CollectionsIterator使用泛型1.为什么使用集合而不是数组&#xff1f;集合和数组相似点都可以存储多个对象&#xff0c;对外作为一个整体存在数组的缺点长度必须…

java异常处理方式推荐做法_谈谈Java异常处理这件事儿

此文已由作者谢蕾授权网易云社区发布。欢迎访问网易云社区&#xff0c;了解更多网易技术产品运营经验。前言我们对于“异常处理”这个词并不陌生&#xff0c;众多框架和库在异常处理方面都提供了便利&#xff0c;但是对于何种处理才是最佳实践&#xff0c;也是众说纷纭。异常处…

java 抽象类 final_final/抽象类/interface

lesson Thirteen                          2018-05-10 02:10:43final:最终的&#xff0c;可以修饰类、属性、方法1.final修饰类&#xff1a;这个类就不能被继承&#xff0c;如&#xff1a;String类&#xff0c;StringBuffer类&#xff0c;System类1…

java io中断_JDK源码阅读:InterruptibleChannel 与可中断 IO

来源&#xff1a;木杉的博客 &#xff0c;imushan.com/2018/08/01/java/language/JDK源码阅读-InterruptibleChannel与可中断IO/Java传统IO是不支持中断的&#xff0c;所以如果代码在read/write等操作阻塞的话&#xff0c;是无法被中断的。这就无法和Thead的interrupt模型配合使…

java值栈_Struts2学习笔记-Value Stack(值栈)和OGNL表达式

只是本人的Struts2学习笔记&#xff0c;关于Value Stack(值栈)和OGNL表达式&#xff0c;把我知道的都说出来&#xff0c;希望对大家有用。一&#xff0c;值栈的作用记录处理当前请求的action的数据。二&#xff0c;小例子有两个action&#xff1a;Action1和Action2Action1有两个…

php 接口日志,PHP 开发 APP 接口--错误日志接口

APP 上线以后可能遇到的问题&#xff1a;① APP 强退② 数据加载失败③ APP 潜在问题错误日志需要记录的内容数据表 error_log 字段&#xff1a;idapp_id&#xff1a;app 类别 iddid&#xff1a;客户端设备号version_id&#xff1a;版本号version_mini&#xff1a;小版本号erro…

php cannot call constructor,安装ECshop普遍问题的解决方法

安装时的问题&#xff1a;1.Strict Standards: Non-static method cls_image::gd_version() should not be called statically in /usr/local/httpd2/htdocs/upload/install/includes/lib_installer.php on line 31解决&#xff1a;找到install/includes/lib_installer.php中的…