线程停止继续_晓龙吊打面试官系列: 如何优雅的停止一个线程

一、什么时候我们需要中断一个线程

在实际的开发中,有很多场景需要我们中断一个正在运行的线程,就比如:

  1. 当我们使用抢票软件时,其中某一个通道已经抢到了火车票,这个时候我们就需要通知其他线程停止工作。

  2. 当我们希望在一个限定的时间里获得任务结果的时候,也需要在任务超时的时候关闭它

但是Java并不能像代码块中的break一样干脆的退出线程运行,Java本身提供的API方法总是让人觉得差强人意,没有办法完美的解决我们的需求,那么我们该如何优雅的停止一个线程呢?

二、如何终止一个线程

1) 暴力停止

1. 使用stop方法停止线程

Java在停止线程的运行方面,提供了一个stop的方法,首先我们写段代码,演示stop方法是如何终止线程的:

package xiao.thread.stop;

public class StopThread {
private static int count;

public static void main(String[] args) throws InterruptedException {
// first :create a thread task
Thread thread = new Thread(() -> {
while (true) {
++count;
// 为了减少打印数 增加个计数判断
//也可调用sleep方法进行休眠
if (count % 100 == 0) {
System.out.println("此时线程依旧存活" + count);
}
}
}, "thread_task for stopThread");
thread.start();
// 如果直接终止 很有可能thread的run方法还没有开始执行 所以建议让main线程休眠一段时间来观察效果
Thread.currentThread().sleep(1_000);
thread.stop();
}
}

首先我们可以明显的观察到,当我们调用stop的时候,线程被暴力停止了。这种方式虽然简单,但是java语言本身并不提倡我们这么做。stop()方法呈删除线状态,已经表明了这个方法已经被弃用。

2. 为什么不提倡使用stop方法

其实不提倡使用stop方法的原因很简单,因为线程最优状态的终止是只能自杀而不能被杀。具体的来说就是:当我们通过其他线程调用stop方法时,此刻我们并不知道被杀死的线程执行到了哪里。就比如我们在做一个为集合添加数据的操作,我们此时无法知道数据的添加进行到了哪一步。而当我们调用stop方法,此时被杀死的线程会立即释放自身持有的锁,其他线程此时就可以看到未被处理完的数据,造成线程安全的问题,破坏了对象的一致性。

2) 捕获异常法

1. 中断机制

中断很好理解,它本身其实并不具备中断的能力,只是一种协作机制。在java中并没有对中断进行任何语法层面的实现,只能够靠我们利用已有的中断标识来记性业务处理达到中断的目的。

2. 相关API

cf77e60e33bf14c872051263e1db1e7e.png
在实现中断功能时,我们常用的API主要是三个方法:

1. public void interrupt
调用此方法会将线程的中断标识设为true2. public boolean isInterrupt
调用此方法将会返回此刻现成的中断标识3.public static boolean interruped
该方法只能通过Thread.interrupted()调用,他会做两个操作
第一步返回当前线程的中断状态
第二部将当前现成的中断标识设为false

3. 证实interrupt没有停止线程的能力

首先我们举个小例子来证明当我们调用interrupt方法时,jvm无法为我们中断目标线程:

public class InterruptThread {

private static int count;

public static void main(String[] args) {
Thread thread = new Thread(() -> {
while (true) {
System.out.println("线程依旧存活:_此时计数器状态为_" + ++count);
try {
Thread.currentThread().sleep(1_000);
} catch (InterruptedException e) {
}

}
},"测试线程");
thread.start();
thread.interrupt();
}

}

我们看下输出结果:825819616f2bd57d43c2e2958e9674d6.png
虽然我们在调用start方法后立即调用了interrupt,但是目标的测试线程依旧每隔一秒在控制台打印了计数器状态,并没有实际的中断线程,通过这个例子我们证明了在我们调用interrupt的时候,jvm并没有立即为我们停止目标线程的运行,如果我们想要在调用interrupt之后停止线程该如何做呢?

4. 异常处理法停止线程

既然我们证实了jvm确实不会主动地替我们中断线程,那么我们就需要利用interrupt方法中提到的中断标识来做一些事情。首先我们需要证明一个新的论点:

当阻塞方法接收到中断信号的时候,会抛出一个InterruptedException 异常

那么我们改造下上面的代码:

public class InterruptThread {
private static int count;
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while (true) {
System.out.println(Thread.currentThread().getName()+"正在运行~");
try {
Thread.currentThread().sleep(2_500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println(Thread.currentThread().getName()+"接收到中断异常");
}
}

}, "测试线程");
thread.start();
Thread.currentThread().sleep(1_000);
thread.interrupt();
}
}

运行结果:bdd1b40668d6dde94840484bc58ceb73.png
在这里我们可以看出,当我们调用interrupt方法时,由于线程此时也调用了可能将线程阻塞的方法sleep,因此此时目标线程在收到我们的中断信号的时候抛出了interruptException,但是由于while方法体里面还有代码需要执行,线程此时并没有结束,这个时候我们就需要利用我们捕获到的异常改造下代码:

public class InterruptThread {
private static int count;
private static boolean mark = true ;
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while (mark) {
System.out.println(Thread.currentThread().getName()+"正在运行~");
try {
Thread.currentThread().sleep(2_500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
mark = false ;
System.err.println(Thread.currentThread().getName()+"接收到中断信号");
}
}
}, "测试线程");
thread.start();
Thread.currentThread().sleep(1_000);
thread.interrupt();
}
}

运行结果:ec0750e4d686ca2f1e5b6ffd6f37b51f.png
可以看出程序在接收到我们的中断信号后就没有继续运行,线程停止。而上面代码的变化其实就发生在while的判断条件上,我们加入了一个布尔变量mark,通过捕获异常停止线程的原理其实也很简单:我们认为的设置一个中断变量,将该值设置为代码运行的入口条件,当我们捕获到异常的时候,改变中断变量的值以达到跳出循环的目的从而实现停止线程。
当然,实现跳出循环的方式有很多,我们也可以通过return关键字实现跳出循环的效果:

public class InterruptThread {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while (true) {
System.out.println(Thread.currentThread().getName()+"正在运行~");
try {
Thread.currentThread().sleep(2_500);
} catch (InterruptedException e) {
e.printStackTrace();
System.err.println(Thread.currentThread().getName()+"接收到中断信号");
return ;
}
}
}, "测试线程");
thread.start();
Thread.currentThread().sleep(1_000);
thread.interrupt();
}
}

运行结果:7cc73c196447fb383527876e6a8fae74.png
通过return也可以很好地利用异常来结束线程的运行。

当然,我们还有个关于interruptException异常的问题要说下

在实际的开发中,我们不能不管不顾的通过抛出异常的方式来结束线程,如果你只是想记录日志,那么此时应该将中断标记重新置为false,以避免程序意外中断

3) 通过守护线程实现(了解即可)

我们也可以利用守护线程的特性来结束一个线程的运行

如果不了解守护线程的特性,可以看我之前的文章
线程的简介

下面我们先看代码实现:

public class DaemonThreadInterrupt {
public static Thread thread;
public boolean flag = true;

public void execut(Runnable task) {
thread = new Thread(()->{
Thread thread2 = new Thread(task,"任务线程");
thread2.setDaemon(true);
thread2.start();
try {
thread2.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
},"执行线程");
thread.start();

}

public void stop() {
thread.interrupt();
}

public static void main(String[] args) {
DaemonThreadInterrupt daemonThreadInterrupt = new DaemonThreadInterrupt();
daemonThreadInterrupt.execut(()->{
while(true) {
System.out.println("程序运行");
try {
thread.sleep(1_000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
daemonThreadInterrupt.stop();
}
}

实现原理:
在上述代码中,我将需要实现的任务线程放到了一个执行线程中,执行线程不负责任何业务逻辑的处理,只负责启动任务线程。而任务线程在启动后,将他设为了执行线程的守护线程。然后让他join到执行线程中。
然后接下来我们就利用了join方法的特性以及守护线程的特点设计了stop方法:
执行join方法后,执行线程将等待任务线程执行完毕后才会执行,但是当我们调用interrupt方法时,join方法接收到中断信号就会抛出异常,此时执行线程不在等待任务线程的运行。重点来了:此时执行线程执行完毕,任务线程作为执行线程的守护线程也结束了他的生命周期。
这种方式充分利用了线程的多种特性停止了一个线程,而且结束的过程可控,也可设置延时结束,适合用作定时任务线程的实现。

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

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

相关文章

数模写作必备利器—latex

数模写作技巧——用latex排版写作 视频地址在我自己的b站 https://www.bilibili.com/video/BV1Pp4y1e7fU/ 数模写作培训

hive读取hdfs存放文件_数据获取层之Flume快速入门(一) 实时监控单个追加文件

实时监控 Hive 日志,并上传到 HDFS 中实现步骤1、Flume 要想将数据输出到 HDFS,必须持有 Hadoop 相关 jar 包commons-configuration-1.6.jar、hadoop-auth-2.7.2.jar、hadoop-common-2.7.2.jar、hadoop-hdfs-2.7.2.jar、commons-io-2.4.jar、htrace-core…

2019-2020中国趋势报告,203页PPT解读16大机会

来源:企鹅智库 报告如下未来智能实验室是人工智能学家与科学院相关机构联合成立的人工智能,互联网和脑科学交叉研究机构。未来智能实验室的主要工作包括:建立AI智能系统智商评测体系,开展世界人工智能智商评测;开展互联…

242.判断一个字符串是否为另一个的乱序 Valid Anagram

错误1"aa""bb"static public bool IsAnagram(string s, string t) { int sLength s.Length; int tLength t.Length; if (sLength ! tLength) { return false; } char c ; int value 0; Dictionary<char, int> d new Dictionary<char, int&g…

行程单图片python预处理_GCC编译过程(预处理-gt;编译-gt;汇编-gt;链接)

前言如果你使用集成环境开发。那么你点击编译按钮就可生成可执行文件。但是C程序从源代码到二进制行程序都经历了那些过程&#xff1f;你知道吗&#xff1f;这些过程集成开发环境在点击编译按钮后都做完了&#xff0c;如果编译没有出错&#xff0c;即可生成可执行文件。本文将以…

树莓派装系统,配置,换源,远程操控

一、树莓派装系统&#xff0c;配置&#xff0c;换源&#xff0c;远程操控 1.装系统 省略 2.各种协议的使能&#xff0c;配置 参考树莓派教程文档 3.换源 3.1系统更新源的更换 sudo nano /etc/apt/sources.list #注释掉原始国外源&#xff0c;即原始文件第一行的代码 #添加…

sqlite查询乘以某列如果是null就换成_大数据之Hive group by with cube/rollup分组查询...

group bysql 查询时&#xff0c;我们常将聚合函数和group by 结合起来对某一个或多个字段进行分组查询&#xff0c;例如&#xff1a;select addcode,count(distinct sbtid)uv from tb_hive_window group by addcode;---------------| addcode | uv |---------------| 0002 …

Windows下MYSQL的安装与配置

配置&#xff1a; 1. 安装MySQL服务 cmd(管理员模式)下切换到MySQL的bin目录&#xff0c;运行 mysqld install 2. 输入 net start mysql 启动服务 3. 输入 mysql -uroot -p 进入&#xff0c;默认无密码 4. 设置密码 mysqladmin -uroot -p 新密码 &#xff08;会提示输入密码&am…

可以操作excel吗_Excel快速填充,这四种方法你会吗?操作逆天告别加班

在Excel的表格制作中&#xff0c;仅仅会复制粘贴可是不够的&#xff0c;还需要掌握更多的技能&#xff0c;来提升我们的工作效率&#xff01;我们在进行Excel报表制作的时候&#xff0c;如果要批量填充序号&#xff0c;有多少种方法呢&#xff1f;下面给大家简单介绍一下这四种…

我的一个树莓派小车项目

如何使用该程序进行开发 1.寻找串口 查看识别串口号 ls -l /dev/tty*找到相应的串口并在程序里修改 ser serial.Serial("/dev/ttyUSB0",9600)2.摄像头 括号里是0还是1取决于摄像头是内部还是外部 不确定的话就挨着尝试&#xff0c;反正就是二选一嘛 #视频捕获…

2G---5G与未来天线技术

本文来源&#xff1a;滤波器过去二十年&#xff0c;我们见证了移动通信从1G到4G LTE的转变。在这期间&#xff0c;通信的关键技术在发生变化&#xff0c;处理的信息量成倍增长。而天线&#xff0c;是实现这一跨越式提升不可或缺的组件。按照业界的定义&#xff0c;天线是一种变…

python操作csv文件第7行开始的数据_Python教程-Python读写CSV文件

前言 本教程学习在Python中使用CSV文件。CSV&#xff08;逗号分隔值&#xff09;格式是在电子表格和数据库中使用的非常流行的导入和导出格式。Python语言包含该模块&#xff0c;该模块具有用于读取和写入CSV格式的数据的类。csv 使用csv.reader&#xff08;&#xff09;读取CS…

leancloud的技术面试指南

面试流程 通常我们的面试分为一次电话面试和一次现场面试。在少数难以决定的时候会多增加一轮电话或现场面试。 面试中的沟通问题 尊重候选人&#xff0c;平等交流&#xff1a;让候选人自我介绍前&#xff0c;先介绍自己和公司&#xff1b;交流的时候双方处于平等的地位&#x…

基于STM32的高精度频率计设计

前言 本文记录了博主完成的一个课设作品&#xff08;学分为3.5分&#xff09;&#xff0c;题目需要利用ARM做出一个高精度频率计。具体要求如下&#xff1a; 1&#xff09;实现对10M以内数字信号频率的高精度测量&#xff0c;频率测量误差不大于0.01%&#xff1b; 2&#xff0…

数学的意义(一)

来源&#xff1a; 数学职业家数学既是一种文化、一种“思想的体操”&#xff0c;更是现代理性文化的核心。马克思说&#xff1a;“一门科学只有当它达到了能够成功地运用数学时&#xff0c;才算真正发展了。”在前几次科技革命中&#xff0c;数学大都起到先导和支柱作用。我们不…

node都会 react_学react需要node吗

学react需要node吗学习react不需要安装node&#xff0c;react.js和node.js没有太大的关联性。完全可以独立的学习react.js。但我们通常都会使用react提供的脚手架搭建项目结构&#xff0c;这个就需要用到node了。但node.js只需要会它的npm安装包就可以了。一、常用工具介绍1. n…

.Net Core 学习资料

官方网站&#xff1a;https://www.microsoft.com/net/core#windows 官方文档&#xff1a;https://docs.asp.net/en/latest/intro.html中文翻译小组&#xff1a;http://www.cnblogs.com/dotNETCoreSG/p/aspnetcore-index.html发布到Jexus&#xff1a;http://www.cnblogs.com/gao…

python弹球小游戏程序_Python实现弹球小游戏

本文主要给大家分享一个实战项目&#xff0c;通过python代码写一款我们儿时大多数人玩过的游戏---小弹球游戏。只不过当时&#xff0c;我们是在游戏机上玩&#xff0c;现在我们通过运行代码来玩&#xff0c;看看大家是否有不一样的体验&#xff0c;是否可以重温当年的乐趣呢&am…

强化学习决策生成-以 Q-learning 为例

强化学习决策生成-以 Q-learning 为例

【数据中台】关于数据中台系统,需要了解哪些技术?

来源&#xff1a;产业智能官国家建材大数据研究中心今天让我们全面解读中台&#xff0c;包括企业为什么要平台化&#xff0c;目前中台都有哪些形式&#xff0c;实施中台系统的优势、面临的问题以及建议都有哪些&#xff1f;中台这个概念早期是由美军的作战体系演化而来的&#…