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

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

这…,这…,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,一经查实,立即删除!

相关文章

[js] js循环中调用异步的方法,如何确保执行结果的顺序是正确的?

[js] js循环中调用异步的方法&#xff0c;如何确保执行结果的顺序是正确的&#xff1f; An example:let searchApi function(arg){return new Promise((resolve,reject)>{setTimeout(()>{console.log(arg)resolve(arg)}, 20)}) }; let fields [1,2,3,4]; let arr new…

Page Cache的落地问题

除非特别说明&#xff0c;否则本文提到的写操作都是 buffer write/write back。 起因 前几天讨论到一个问题&#xff1a;Linux 下文件 close成功&#xff0c;会不会触发 “刷盘”&#xff1f; 其实这个问题根本不用讨论&#xff0c;查一下就知道。 man 2 close 的 NOTES 一节里…

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

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

[js] 写一个方法遍历指定对象的所有属性

[js] 写一个方法遍历指定对象的所有属性 Object.keys()、Object.values()只能遍历对象自有的属性&#xff0c;for in 可以遍历原型中的属性。个人简介 我是歌谣&#xff0c;欢迎和大家一起交流前后端知识。放弃很容易&#xff0c; 但坚持一定很酷。欢迎大家一起讨论 主目录 …

jquery(2)

获得内容 - text()、html() 以及 val() 三个简单实用的用于 DOM 操作的 jQuery 方法&#xff1a;text() - 设置或返回所选元素的文本内容 ----不能识别标签 html() - 设置或返回所选元素的内容&#xff08;包括 HTML 标记&#xff09; val() - 设置或返回表单字段的值----- 即登…

[js] 实现一个函数记忆的方法

[js] 实现一个函数记忆的方法 同步运算结果缓存&#xff0c;这个就老生常谈了&#xff1a; function useCache(func) {var cache {};return function() {var key arguments.length Array.prototype.join.call(arguments);if(cache[key]) return cache[key];cache[key] fu…

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 …

[js] 手写一个trim()的方法

[js] 手写一个trim()的方法 function trim(str) { if (str[0] && str[str.length - 1] ) { return trim(str.substring(1, str.length - 1)) } else if (str[0] ! && str[str.length - 1] ) { return trim(str.substring(0, str.length - 1)) } else…

微信小程序里如何使用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…

[js] 你是如何比较js函数的执行速度的?

[js] 你是如何比较js函数的执行速度的&#xff1f; 采用chrome performance apiconsole.time(flag); console.timeEnd(flag);performance api比较精准的 console.time(flag); 也行 搭配timelog 多次测量个人简介 我是歌谣&#xff0c;欢迎和大家一起交流前后端知识。放弃很容…

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…

[js] axios拦截器原理是什么?

[js] axios拦截器原理是什么&#xff1f; 拦截器原理其实就是用use添加用户自定义的函数到拦截器的数组中。 最后把他们放在拦截器请求前&#xff0c;请求后。组成promise链式调用。 // 组成Promise链// Hook up interceptors middleware// 把 xhr 请求 的 dispatchRequest 和…

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…

[js] fetch和axios请求的原理都是基于XMLHttpRerequst吗?

[js] fetch和axios请求的原理都是基于XMLHttpRerequst吗&#xff1f; axios是基于XMLHttpRequest的封装&#xff0c;而fetch是js原生支持的网络请求api&#xff0c;这在浏览器底层进行了支持。个人简介 我是歌谣&#xff0c;欢迎和大家一起交流前后端知识。放弃很容易&#x…

LeetCode(81): 搜索旋转排序数组 II

Medium&#xff01; 题目描述&#xff1a; 假设按照升序排序的数组在预先未知的某个点上进行了旋转。 ( 例如&#xff0c;数组 [0,0,1,2,2,5,6] 可能变为 [2,5,6,0,0,1,2] )。 编写一个函数来判断给定的目标值是否存在于数组中。若存在返回 true&#xff0c;否则返回 false。 示…

网站三级分销数据库如何设计,简单案例

一、问题产生 有小伙伴微信私信我&#xff0c;说老板想设计一套三级返佣的微信淘宝客裂变系统&#xff0c;然后问我怎么搞… 咳咳&#xff0c;对于三级分销的数据库设计&#xff0c;相信很多小伙伴头疼的可能不是设计上&#xff0c;而是查询上&#xff0c;因为通常涉及到会员…

[js] 举例说明面向对象编程有什么缺点?

[js] 举例说明面向对象编程有什么缺点&#xff1f; 有实例化开销&#xff0c;内存消耗比较大&#xff0c;性能消耗比较大个人简介 我是歌谣&#xff0c;欢迎和大家一起交流前后端知识。放弃很容易&#xff0c; 但坚持一定很酷。欢迎大家一起讨论 主目录 与歌谣一起通关前端…

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

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