如何暂停一个正在运行的线程?

今天把小伙伴问懵了,小刚,你知道怎么停止一个线程吗?

这…,这…,stop?

原来平时小刚这小子只知道创建线程,不知道怎么暂停线程呀~[狗头]


停止线程是在多线程开发中很重要的技术点,比如在多线程持续处理业务代码时,由于处理逻辑中有第三方接口异常,我们就假设发送短信接口挂了吧,那么此时多线程调用短信接口是没有任何意义的,我们希望接口恢复后再对接口进行处理,那么此时怎么办呢,如何中止已经启动的线程呢?

其实在Java中有3种方式可以终止正在运行的线程:

  1. 使用stop方法强制退出:使用stop()方法强制终止线程,注意,强烈不推荐这种方式,并且该方法已经被标记为过期方法了。
  2. 使用interrupt方法中断线程,该方法只是告诉线程要终止,但最终何时终止取决于计算机;
  3. 设置标志位:使用设置退出标志,使线程正常退出,也就是当run方法完成后线程终止;

尽管罗列了三种方式,但由于存在安全问题,所以舍弃了stop()方法,怎么就安全问题了呢?

暴力停止线程的stop()方法「禁止使用」

之所以说stop()方法暴力是相对于其他两种方式的,只要调用stop()方法,运行中的线程就暂停了,我们通过一段代码测试一下:

public class MyTest {public static void main(String[] args) {try {/**创建线程**/ThreadDemo demo = new ThreadDemo();/**开启线程**/demo.start();/**线程休眠**/Thread.sleep(5000);/**停止线程**/demo.stop();} catch (InterruptedException e) {e.printStackTrace();}}}public class ThreadDemo extends Thread{/**变量i**/private int i = 0;@Overridepublic void run() {try {while (true){i++;System.out.println("输出i:"+i);Thread.sleep(1000);}}catch (InterruptedException e){System.out.println("抛出异常");}}
}

执行结果如下:

输出i:1
输出i:2
输出i:3
输出i:4
输出i:5

如上,我们创建了一个死循环输出的线程ThreadDemo,每隔一秒输出i++,但是当遇到stop()方法后,就不再输出了,不对,看上去没问题呀,stop() 方法这不用的好好的吗?

嗨,怪就怪这个例子太简单了吧,我们来看看弄点带操作对象的例子,首先创建一个用户实体:

public class UserModel {/*** 给定userName+password默认值* 用于模拟上一个线程给赋的旧值*/private String userName = "张三";private String password = "hahahha";/*** 用于复制的方法* 为防止多线程数据错乱,加上synchronized关键字* @param userName* @param password*/synchronized public void setValue(String userName, String password){try {this.userName = userName;Thread.sleep(3000);this.password = password;} catch (InterruptedException e) {e.printStackTrace();}}省略get\set方法...
}

然后我们再在ThreadDemo中使用这个实体:

public class ThreadDemo extends Thread{private UserModel userModel;public ThreadDemo(UserModel userModel){this.userModel = userModel;}@Overridepublic void run() {/*** 重新设置用户名+密码* 用户名:niceyoo* 密码:123456*/userModel.setValue("niceyoo","123456");}
}

然后在MyTest中创建并启动线程,然后调用stop()方法:

public class MyTest {public static void main(String[] args) {try {/**创建用户实体**/UserModel userModel = new UserModel();/**创建线程**/ThreadDemo demo = new ThreadDemo(userModel);/**开启线程**/demo.start();/**线程休眠**/Thread.sleep(1000);/**停止线程**/demo.stop();/**输出用户实体**/System.out.println(userModel.getUserName() + " :" + userModel.getPassword());} catch (ThreadDeath | InterruptedException e) {e.printStackTrace();}}}

输出结果如下:

niceyoo :hahahha

显然跟我们预期的输出结果niceyoo\123456不一致,使用stop()释放锁,对锁定的对象进行了解锁,导致数据得不到同步的处理,出现数据不一致的情况,所以这样就会导致数据安全问题,这也是现在为何 stop() 方法被标注为 “作废、过期”。

interrupted()方法「只告诉要停止,不知道何时停」

使用interrupted()方法就不像是stop()方法那样简单粗暴了,调用该方法仅仅是在当前线程中打了一个停止的标记,并不是真的停止线程,就好比,我打电话告诉你不要玩游戏了,但是你什么时候停止玩游戏就是你的事了。

public class MyTest {public static void main(String[] args) {try {/**创建线程**/ThreadDemo2 demo = new ThreadDemo2();/**开启线程**/demo.start();/**线程休眠**/Thread.sleep(2000);/**停止线程**/demo.interrupt();} catch (InterruptedException e) {System.out.println("线程已经暂停");e.printStackTrace();}}}public class ThreadDemo2 extends Thread{@Overridepublic void run() {try {for (int i = 0; i < 1800000; i++) {if(!this.isInterrupted()){System.out.println("输出i:"+i++ + " - 线程未停止 ");}else{System.out.println("输出i:"+i++ + " - 线程已停止 - 抛出异常");throw new InterruptedException();}}}catch (InterruptedException e){System.out.println("线程已结束...");}}
}

输出结果:

输出i:1499992 - 线程未停止 
...
输出i:1700624 - 线程未停止 
输出i:1700626 - 线程未停止 
输出i:1700628 - 线程已停止 - 抛出异常
线程已结束...

简单说一下上方代码,首先我们创建了一个for循环输出i++的线程,启动线程后调用 interrupt() 方法停止线程,但是啥时候停止是不可控的,虽然不可控但是还是有方法知道线程是否是停止的,我们在ThreadDemo2线程类中看到 if 判断 — this.isInterrupted() 「等价于Thread.currentThread().isInterrupt() 」,这是用来判断当前线程是否被终止,通过这个判断我们可以做一些业务逻辑处理,通常如果this.isInterrupted被判定为true后,我们会抛一个中断异常,然后通过try-catch捕获。

再额外说一下,有的小伙伴设置的 for 循环变量的最大值比较小,测试执行过程中并没有重现线程被终止,然后就怀疑这个 interrupt() 到底能不能停止线程呀, 不用纠结,这正是线程的自主权,我们无法像 stop() 方法一样直接停止线程的。

设置标志位

设置标志位是用到了共享变量的方式,我们了解线程对于变量的操作都是操作的变量副本,而一旦使用

volatile关键字修饰后,因为其可见性,变量变更始将终从主存中获取最新值。

public class MyTest {public static void main(String[] args) {/**创建2个线程**/ThreadDemo3 demo1 = new ThreadDemo3();ThreadDemo3 demo2 = new ThreadDemo3();demo1.setName("线程1");demo2.setName("线程2");/**开启线程**/demo1.start();demo2.start();/**让线程先运行5s**/try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}/**修改线程的变量**/demo1.heartbeat = false;demo2.heartbeat = false;System.out.println("----暂停线程----");/**让线程再运行5s**/try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}/**再将标志为置为true**/demo1.heartbeat = true;demo2.heartbeat = true;System.out.println("----从新开启线程----");}
}public class ThreadDemo3 extends Thread{/**共享变量**/volatile Boolean heartbeat = true;@Overridepublic void run() {while (true){/**判断标志是否为true**/if (heartbeat){System.out.println("当前运行线程为:" +Thread.currentThread().getName() + " - 运行");}else{System.out.println("当前运行线程为:" +Thread.currentThread().getName() + " - 非运行");}try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}

输出结果:

省略ing...
当前运行线程为:线程1 - 运行
当前运行线程为:线程2 - 运行
----暂停线程----
省略ing...
当前运行线程为:线程1 - 非运行
当前运行线程为:线程2 - 非运行
----从新开启线程----
当前运行线程为:线程1 - 运行
当前运行线程为:线程2 - 运行
省略ing...

来看一下上方代码,我们在线程类里创建了共享变量heartbeat,因为要监听这个贡献变量的状态,肯定是要用while循环体了,为了演示状态的变更,所以在while循环体代码中没有throw抛出 InterruptedException 异常,正常情况下在判断共享变量为fasle时,也是要手动抛出异常的,ok,这就是设置标志位了。

总结一定要看的

stop()方法在这就不提了,肯定是行不通的,至于为何不能使用大家可以再仔细看看上方那个例子。

然后是interrupt()方法+抛异常处理,看完上边那个例子,大家可能会觉得这个方法有点问题,暂停线程完全靠线程自身决定,即便调用了也不能快速的停止线程,但是我要告诉你,这是目前最为正确的方式… 咳咳,别着急,咱先把设置标志位说了。

设置标志位使用了volatile关键字共享变量方式,通过改变共享变量+抛异常的方式来暂停线程,这个看起来最有效,最正确的方式,其实有一点点问题,而这一点点问题就是为什么让 interrupt() 成为最正确的方式。

volatile标记共享变量方式,在线程发生阻塞时是无法完成响应的。

这个所谓的阻塞指的是什么呢?

其实发生阻塞的情况是比较常见的,比如调用 Thread.join() 方法「当前线程陷入无限期的阻塞,join() 所属的线程对象正常运行run()方法,对join()方法不了解的小伙伴可以去百度了」,或者是 Thread.sleep() 方法,再或者是线程需要等待键盘输入而被阻塞,还有socket网络编程中的 ServerSocket.accept() 方法等等等,总之,在这些种种情况下让线程处于不可运行状态时,即便是主线程修改了共享变量的值,该线程此时根本无法检查循环标志,所以也就无法实现线程中断。

所以,interrupt() + 手动抛异常的方式是目前中断一个正在运行的线程最为正确的方式了。

如果觉得这篇文章对你有用,可以左上角关注一下我呀~

关注我的公众号吧,与一万+小伙伴一起成长~

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

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

相关文章

Docker中搭建FastDFS文件系统(多图)

关于FastDFS FastDFS 是以 C 语言开发的一项开源轻量级分布式文件系统&#xff0c;他对文件进行管理&#xff0c;主要功能有&#xff1a;文件存储&#xff0c;文件同步&#xff0c;文件访问&#xff08;文件上传/下载&#xff09;等&#xff0c;特别适合以文件为载体的在线服务…

SpringBoot集成FastDFS依赖实现文件上传

前言 对FastDFS文件系统安装后的使用。 FastDFS的安装请参考这篇&#xff1a;https://www.cnblogs.com/niceyoo/p/13511082.html 本文环境&#xff1a;IDEA JDK1.8 Maven 1、引入依赖 简单说一下这个依赖部分&#xff0c;目前大部分都是采用的如下依赖&#xff1a; <…

SQL取最大值编码(自动编码)

SQL取最大值编码(自动编码) 用途 : 使用SQL语法做出自动编码效果&#xff0c;例如将单号自动1后&#xff0c;产生该笔单号 Table说明 SQL语法 SELECT AREPLICATE(0,7-len(convert(varchar,((MAX(right(ae002,7)))1))))(convert(varchar,((MAX(right(ae002,7)))1))) from ygmae …

微信小程序里如何使用npm?小程序集成友盟举例

1、执行npm初始化指令 小程序根目录&#xff0c;命令执行如下指令&#xff1a; npm init执行后会让加载项目初始信息&#xff0c;具体截图如下&#xff1a; 2、执行安装npm包指令 在这我们举个例子&#xff0c;以接入友盟统计SDK为例&#xff0c;执行命令如下&#xff1a;…

Spring MVC 5 + Thymeleaf 基于Java配置和注解配置

Spring MVC 5 Thymeleaf 注解配置 Spring的配置方式一般为两种&#xff1a;XML配置和注解配置 Spring从3.0开始以后&#xff0c;推荐使用注解配置&#xff0c;这两种配置的优缺点说的人很多&#xff0c;我就不说了&#xff0c;自行体会&#xff0c;下面就用注解配置实现一个Sp…

docker镜像无法删除 Error:No such image:xxxxxx

1、前言 docker镜像无法删除&#xff0c;通过 docker images 查看镜像明明存在就是删除不了。 删除提示&#xff1a;Error&#xff1a;No such image&#xff1a;xxxxxxx 具体截图内容如下&#xff1a; 2、解决方法 进入目录&#xff1a; cd /var/lib/docker/image/over…

python中集合set,字典dict和列表list的区别以及用法

python中set代表集合&#xff0c;list代表列表,dict代表字典 set和dict的区别在于&#xff0c;dict是存储key-value&#xff0c;每一个key都是唯一的&#xff0c;set相对于dict存储的是key&#xff0c;且key是唯一的,list除了变量外都可以存储 dict{"a":1,"b&qu…

第6课 仿Siri机器人-语音朗读和语音识别

一、功能设计输入文本&#xff0c;单击“朗读”按钮&#xff0c;由手机读出该文本&#xff08;如果没有输入文本&#xff0c;则弹出消息框警告“请输入文本&#xff09;&#xff1b;单击“识别”按钮&#xff0c;读入语音&#xff0c;从文本框中输出文字。&#xff08;另&#…

口述完SpringMVC执行流程,面试官就让同事回家等消息了

Srping MVC 执行流程真的是老生常谈的话题了&#xff0c;最近同事小刚出去面试&#xff0c;前面面试官相继问了几个 Spring 相关的问题&#xff0c;但当面试官问他&#xff0c;你知道 Srping MVC 的执行流程吗&#xff1f;小刚娴熟的巴拉巴拉回答完后&#xff0c;面试官就让他回…

C++ 判断系统大小字节序

bool IsLitterEndian() {union UTest{std::uint16_t t;std::uint8_t c;} endianTest{ 0x01 };return (endianTest.c 0x01); } 转载于:https://www.cnblogs.com/fluteary/p/9178627.html

macos brew zookeeper,安装后zookeeper启动失败?

一、Zookeeper安装流程 执行如下安装命令&#xff1a; brew install zookeeper执行截图如下&#xff1a; 安装后查看 zookeeper 安装信息&#xff08;默认拉取最新版本&#xff09; brew info zookeeper执行截图如下&#xff1a; 二、Zookeeper启动、状态查询、及关闭 启…

为什么SimpleDateFormat不是线程安全的?

一、前言 日期的转换与格式化在项目中应该是比较常用的了&#xff0c;最近同事小刚出去面试实在是没想到被 SimpleDateFormat 给摆了一道… 面试官&#xff1a;项目中的日期转换怎么用的&#xff1f;SimpleDateFormat 用过吗&#xff1f;能说一下 SimpleDateFormat 线程安全问…

【Python 学习_第2周_程序代码】金角大王培训第二周练习_购物车代码,将写的代码和老师代码比较,记录下收获...

培训第二周&#xff0c;课堂练习为编写一段购物车代码&#xff0c;需求描述如下&#xff1a; 1.提示用户输入薪水 2.用户输入薪水后&#xff0c;打印商品编号、内容及价格 3.提醒用户输入商品代码&#xff0c;若余额大于等于商品价格&#xff0c;可购买&#xff1b;若小于&…

ActiveMQ Cannot send, channel has already failed: tcp:127.0.0.1:8161

仅针对如下错误内容&#xff1a; Cannot send, channel has already failed: tcp://127.0.0.1:8161一种尝试解决&#xff0c;修改连接端口为 61616&#xff1a; tcp://127.0.0.1:61616在没有修改过 ActiveMQ 配置文件情况下&#xff0c;默认 tcp 端口为 61616&#xff0c;htt…

pip安装报错处理+PyPi源切换教程

一、pip安装出错类型 1.1 pip版本过旧导致不能安装 报错提示&#xff1a; You are using pip version 9.0.3, however version 10.0.1 is available. You should consider upgrading via the python -m pip install --upgrade pip comm and. 可通过以下命令升级pip python -m p…

面试官:说一下List排序方法

1. 前言 排序算是比较高频的面试题了&#xff0c;节前面试了的两家公司都有问到排序问题&#xff0c;整理后分享给大家&#xff08;文末见总结&#xff09;。 通常我们想到实现排序就是 Collections 工具类的 sort() 方法&#xff0c;而 sort() 方法有两种&#xff1a; 直接调…

python之路——内置函数和匿名函数

楔子 在讲新知识之前&#xff0c;我们先来复习复习函数的基础知识。 问&#xff1a;函数怎么调用&#xff1f; 函数名() 如果你们这么说。。。那你们就对了&#xff01;好了记住这个事儿别给忘记了&#xff0c;咱们继续谈下一话题。。。 来你们在自己的环境里打印一下自己的名字…

SpringBoot打包成Docker镜像

1. 本文环境 Maven&#xff1a;3.6.3 &#xff08;Maven配置参考&#xff09; SpringBoot version&#xff1a;2.3.4.RELEASE Docker version&#xff1a; 19.03.11 &#xff08;Docker搭建参考&#xff09; JDK version&#xff1a;1.8.0_221 &#xff08;JDK搭建参考&…

Redis分布式锁—SETNX+Lua脚本实现篇

前言 平时的工作中&#xff0c;由于生产环境中的项目是需要部署在多台服务器中的&#xff0c;所以经常会面临解决分布式场景下数据一致性的问题&#xff0c;那么就需要引入分布式锁来解决这一问题。 针对分布式锁的实现&#xff0c;目前比较常用的就如下几种方案&#xff1a;…

Windows10远程报错:由于CredSSP加密Oracle修正

https://support.microsoft.com/zh-cn/help/4093492/credssp-updates-for-cve-2018-0886-march-13-2018 参照官方更新文件&#xff1a;查找办法 https://support.microsoft.com/zh-cn/help/4093492/&#xff0c;4093492是更新包kb后面的数字 修改办法&#xff1a;下图参照注册表…