震惊!这样终止线程,竟然会导致服务宕机?

在开始之前,我们先来看以下代码会有什么问题?

public class ThreadStopExample {public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {try {System.out.println("子线程开始执行");// 模拟业务处理Thread.sleep(1000);} catch (Exception e) { }// 伪代码:重要的业务方法System.out.println("子线程的重要业务方法");});t1.start();// 让子线程先运行一点业务Thread.sleep(100);// 终止子线程t1.stop();// 等待一段时间,确保子线程“执行完”Thread.sleep(3000);System.out.println("主线程执行完成");}
}

或许你已经发现了,上面这段代码使用了 Thread.stop() 来终止线程,在 Java 程序中是不允许这样终止线程的。什么?你问为什么不能这样?

首先来说 IDE 都会鄙视你了,它会阻止你使用 Thread.stop()

什么?你不信。那么来看这张图:
image.png

好吧,那为什么不能这样用呢?总得给我一个敷衍的理由吧?

问题一:破坏了程序的完整性

其实是这样的,以文章刚开头的那段代码来说,它的执行结果是:

子线程开始执行

主线程执行完成

我们发现了一个惊天的大问题,最重要的那段伪代码竟然没执行,如下图所示:
image.png

可以看出使用 stop() 终止线程之后,线程剩余的部分代码会放弃执行,这样会造成严重的且不易被发现的惊天大 Bug,假如没有执行的那段代码是释放系统资源的代码,或者是此程序的主要逻辑处理代码。这就破坏了程序基本逻辑的完整性,导致意想不到的问题发生,而且它还很隐秘,不易被发现和修复。

有人说,这还不简单,我加个 finally 不就完了吗?

这???杠精哪都有,今年特别多。

行,既然这个说服不了你,咱接着往下看。

问题二:破坏了原子逻辑

我们知道在 Java 中 synchronized 属于独占式可重入悲观锁,如果我们使用它修饰代码,妥妥的多线程没问题,但如果碰到 stop() 方法就不一定了,直接来看代码吧。

public class ThreadStopExample {public static void main(String[] args) throws InterruptedException {MyThread myThread = new MyThread();Thread t2 = new Thread(myThread);// 开启线程t2.start();for (int i = 0; i < 10; i++) {Thread t = new Thread(myThread);t.start();}// 结束线程t2.stop();}/*** 自定义原子测试线程*/static class MyThread implements Runnable {// 计数器int num = 0;@Overridepublic void run() {// 同步代码块,保证原子操作synchronized (MyThread.class) {// 自增num++;try {// 线程休眠 0.1 秒Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}// 自减num--;System.out.println(Thread.currentThread().getName() + " | num=" + num);}}}
}

以上程序的执行结果为:

Thread-5 | num=1

Thread-4 | num=1

Thread-2 | num=1

Thread-1 | num=1

Thread-8 | num=1

Thread-6 | num=1

Thread-9 | num=1

Thread-3 | num=1

Thread-7 | num=1

Thread-10 | num=1

从结果可以看出,以上代码经过 synchronized 修饰的 ++ 和 – 操作,到最后打印的结果 num 竟然不是 0,而是 1。

这是因为 stop() 方法会释放此线程中的所有锁,导致程序执行紊乱,破坏了程序的原子操作逻辑

以上的这些问题,导致了 JDK 废弃了 stop() 的方法,它的废弃源码如下:

/*** Forces the thread to stop executing.* <p>* If there is a security manager installed, its <code>checkAccess</code>* method is called with <code>this</code>* as its argument. This may result in a* <code>SecurityException</code> being raised (in the current thread).* <p>* If this thread is different from the current thread (that is, the current* thread is trying to stop a thread other than itself), the* security manager's <code>checkPermission</code> method (with a* <code>RuntimePermission("stopThread")</code> argument) is called in* addition.* Again, this may result in throwing a* <code>SecurityException</code> (in the current thread).* <p>* The thread represented by this thread is forced to stop whatever* it is doing abnormally and to throw a newly created* <code>ThreadDeath</code> object as an exception.* <p>* It is permitted to stop a thread that has not yet been started.* If the thread is eventually started, it immediately terminates.* <p>* An application should not normally try to catch* <code>ThreadDeath</code> unless it must do some extraordinary* cleanup operation (note that the throwing of* <code>ThreadDeath</code> causes <code>finally</code> clauses of* <code>try</code> statements to be executed before the thread* officially dies).  If a <code>catch</code> clause catches a* <code>ThreadDeath</code> object, it is important to rethrow the* object so that the thread actually dies.* <p>* The top-level error handler that reacts to otherwise uncaught* exceptions does not print out a message or otherwise notify the* application if the uncaught exception is an instance of* <code>ThreadDeath</code>.** @exception  SecurityException  if the current thread cannot*               modify this thread.* @see        #interrupt()* @see        #checkAccess()* @see        #run()* @see        #start()* @see        ThreadDeath* @see        ThreadGroup#uncaughtException(Thread,Throwable)* @see        SecurityManager#checkAccess(Thread)* @see        SecurityManager#checkPermission* @deprecated This method is inherently unsafe.  Stopping a thread with*       Thread.stop causes it to unlock all of the monitors that it*       has locked (as a natural consequence of the unchecked*       <code>ThreadDeath</code> exception propagating up the stack).  If*       any of the objects previously protected by these monitors were in*       an inconsistent state, the damaged objects become visible to*       other threads, potentially resulting in arbitrary behavior.  Many*       uses of <code>stop</code> should be replaced by code that simply*       modifies some variable to indicate that the target thread should*       stop running.  The target thread should check this variable*       regularly, and return from its run method in an orderly fashion*       if the variable indicates that it is to stop running.  If the*       target thread waits for long periods (on a condition variable,*       for example), the <code>interrupt</code> method should be used to*       interrupt the wait.*       For more information, see*       <a href="{@docRoot}/../technotes/guides/concurrency/threadPrimitiveDeprecation.html">Why*       are Thread.stop, Thread.suspend and Thread.resume Deprecated?</a>.*/
@Deprecated
public final void stop() {SecurityManager security = System.getSecurityManager();if (security != null) {checkAccess();if (this != Thread.currentThread()) {security.checkPermission(SecurityConstants.STOP_THREAD_PERMISSION);}}// A zero status value corresponds to "NEW", it can't change to// not-NEW because we hold the lock.if (threadStatus != 0) {resume(); // Wake up thread if it was suspended; no-op otherwise}// The VM can handle all thread statesstop0(new ThreadDeath());
}

可以看出 stop() 方法被 @Deprecated 注释修饰了,而被此注解修饰的代码表示为过时方法,不建议被使用。从 stop() 的备注信息可以看出,官方也不建议使用 stop() ,说它是一个非安全的方法。

正确终止线程

那如何终止线程呢?这里提供 2 个正确的方法:

  1. 设置退出标识退出线程;
  2. 使用 interrupt() 方法终止线程。

1.自定义退出标识

我们可以自定义一个布尔变量来标识是否需要退出线程,实现代码如下:

// 自定义退出标识退出线程
static class FlagThread extends Thread {public volatile boolean exit = false;public void run() {while (!exit) {// 执行正常的业务逻辑}}
}

可以看出我们使用了关键字 volatile 对线程进行了修饰,这样就可以保证多线程的执行安全了,在我们需要让线程退出时,只需要把变量 exit 赋值为 true 就可以了。

2.interrupt 终止线程

当我们使用 interrupt() 方法时,以上两个示例的执行结果就正常了,执行代码如下:

public class ThreadStopExample {public static void main(String[] args) throws InterruptedException {// 问题一:破坏了程序的完整性Thread t1 = new Thread(() -> {try {System.out.println("子线程开始执行");// 模拟业务处理Thread.sleep(1000);} catch (Exception e) { }// 伪代码:重要业务方法System.out.println("子线程的重要业务方法");});t1.start();// 让子线程先运行一点业务Thread.sleep(100);// 终止子线程t1.interrupt();// 等待一段时间,确保子线程“执行完”Thread.sleep(3000);System.out.println("主线程执行完成");// 问题二:破坏了原子逻辑MyThread myThread = new MyThread();Thread t2 = new Thread(myThread);// 开启线程t2.start();for (int i = 0; i < 10; i++) {Thread t = new Thread(myThread);t.start();}// 结束线程t2.interrupt();}/*** 自定义原子测试线程*/static class MyThread implements Runnable {// 计数器int num = 0;@Overridepublic void run() {// 同步代码块,保证原子操作synchronized (MyThread.class) {// 自增num++;try {// 线程休眠 0.1 秒Thread.sleep(100);} catch (InterruptedException e) {System.out.println(e.getMessage());}// 自减num--;System.out.println(Thread.currentThread().getName() + " | num=" + num);}}}
}

以上程序的执行结果为:

子线程开始执行

子线程的重要业务方法

主线程执行完成

sleep interrupted

Thread-1 | num=0

Thread-9 | num=0

Thread-10 | num=0

Thread-7 | num=0

Thread-6 | num=0

Thread-5 | num=0

Thread-4 | num=0

Thread-2 | num=0

Thread-3 | num=0

Thread-11 | num=0

Thread-8 | num=0

可以看出以上的执行都符合我们的预期,这才是正确的终止线程的方式。

总结

本文我们讲了线程的三种终止方式,自定义退出标识的方式、使用 stop() 的方式或 interrupt() 的方式。其中 stop() 的方式会导致程序的完整性和原子性被破坏的问题,并且此方法被 JDK 标识为过期方法,不建议使用,而 interrupt() 方法无疑是最适合我们的终止线程的方式。

更多精彩内容,请关注微信公众号「Java中文社群」

Java中文社群公众号二维码

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

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

相关文章

华为交换机系统软件升级和安全漏洞修复教程

华为交换机官网 可以查询到华为交换机官方电话:400-822-9999,通过下面2个命令查询出需要升级的交换机软件版本和补丁版本号发给华为,获取新的升级系统软件和补丁以及升级教程。下面是我升级华为交换机总结的教程,作为参考 <HUAWEI> display version<HUAWEI&g…

想读Spring源码?先从这篇「 极简教程」开始

这是我的第 47 篇原创文章。为什么要阅读源码&#xff1f;这是一个有趣的问题&#xff0c;类似的问题还有&#xff0c;为什么要看书&#xff1f;为什么要爬山&#xff1f;这也是一个哲学问题&#xff0c;我想每个人都有不同的答案&#xff0c;下面我是对阅读源码好处的一些思考…

Linux Shell接收键盘输入

1.read命令格式 read [选项] [变量名] 选项&#xff1a; -p “提示信息”&#xff1a;在等待read输入时&#xff0c;输出提示信息 -t “秒数”&#xff1a; read命令会一致等待用户输入&#xff0c;使用此选项可以指定等待时间 -n “字符数”&#xff1a; read命令只接受指…

想快速拥有个人网站?来试试这个...

一、简介Hugo 是Go语言实现的一款静态网站生成器。它简单、易用、高效、易扩展、快速部署。相比较其他静态网站生成器&#xff0c;它的优点有这几点&#xff1a;项目构建特别快主题目录与站点目录结构一样配置文件为*.toml 格式&#xff0c;语法常简单易懂&#xff0c;没有缩进…

Java对象都是在堆上分配空间吗?答案竟然是...

作者 l Hollis来源 l Hollis&#xff08;ID&#xff1a;hollischuang&#xff09;Java作为一种面向对象的&#xff0c;跨平台语言&#xff0c;其对象、内存等一直是比较难的知识点&#xff0c;所以&#xff0c;即使是一个Java的初学者&#xff0c;也一定或多或少的对JVM有一些了…

服务器运行容器工具大盘点!

服务器到底是什么&#xff1f;服务器的硬件好理解&#xff0c;其实就是一台性能、稳定性、扩展性等等比我们普通个人PC强的一台机器而已&#xff0c;它也需要搭载操作系统&#xff0c;比如有专门的Windows Server或者各种Linux发行版系统。只不过咱这里很多小伙伴可能还是处于学…

想读Spring源码?先从这篇「 极简教程」开始吧...

为什么要阅读源码&#xff1f;这是一个有趣的问题&#xff0c;类似的问题还有&#xff0c;为什么要看书&#xff1f;为什么要爬山&#xff1f; 这也是一个哲学问题&#xff0c;我想每个人都有不同的答案&#xff0c;下面我是对阅读源码好处的一些思考。 &#xff08;PS&#x…

ALP规则的验证

实验1&#xff1a;ALP规则的验证实验目标创建本地用户jack、tom、mike,创建本地组group1&#xff0c;并把所建的用户加入到group1中&#xff0c;通过ALP规则实现以上用户对e:\share\1.txt 文件内容读取和写入权限实验环境略实验步骤一、 创建本地用户jack、tom、mike右击计算机…

6大分布式定时任务对比

作者 | sharedCode来源 | blog.csdn.net/u012394095/article/details/79470904分布式定时任务简介 把分散的&#xff0c;可靠性差的计划任务纳入统一的平台&#xff0c;并实现集群管理调度和分布式部署的一种定时任务的管理方式&#xff0c;叫做分布式定时任务。常见开源方案 e…

Python利用pandas获取每行最大值和最小值

知识点 1.找出每一行最大值和对应的列索引&#xff1a; #找出每行最大值对应的索引 df[max_idx]df.idxmax(axis1) #取出该最大值 df[max_val]df.max(axis1)2.找出每一行最小值和对应的列索引&#xff1a; # 找出每行最小值对应的索引 df[min_idx]df.idxmin(axis1) # 取出该最…

一文搞懂 ThreadLocal 原理

当多线程访问共享可变数据时&#xff0c;涉及到线程间同步的问题&#xff0c;并不是所有时候&#xff0c;都要用到共享数据&#xff0c;所以就需要线程封闭出场了。数据都被封闭在各自的线程之中&#xff0c;就不需要同步&#xff0c;这种通过将数据封闭在线程中而避免使用同步…

单域MPLS ***数据转发实验分析

MPLS 数据详细转发流程示意图&#xff1a;配置思路&#xff1a;在自治系统AS100中配置IGP&#xff0c;使得两台PE路由器的lo 0接口路由可达&#xff1b;两台PE路由器路由可达后&#xff0c;在两台PE路由器之间建立MP-iBGP邻居关系&#xff0c;用来传输V4路由&#xff1b;AS100中…

IDEA 终于支持中文版和 JDK 直接下载了(太方便了)附新版介绍视频

这是我的第 48 篇原创文章。IDEA 2020.1 经过了漫长的打磨终于发布正式版了&#xff0c;而这次的版本不止直接支持 Java 14&#xff0c;还带来了两个重量级的功能&#xff1a;官方中文版支持和 JDK 直接下载。在之前的开发中&#xff0c;当我们需要下载 JDK 时&#xff0c;通常…

【BO】WEBI文件打开时提示Illegal access错误

在infoview中打开WEBI文件时&#xff0c;提示如下错误。 通过查看SCN&#xff0c;找到错误原因是CMC中有一个服务没有启动。 启动这个服务即可&#xff1a; WebIntelligenceProcessingServer转载于:https://www.cnblogs.com/uzipi/p/3905513.html

华为交换机链路聚合使用ENSP模拟器进行实验

一、简介 链路聚合(Eth-Trunk)是将多个物理接口捆绑为一个逻辑接口,实现增加链路带宽,提高可靠性,提供负载分担的目的。 二、华为ENSP模拟器仿真图 华为交换机LSW1和LSW2端口G0/0/2、G0/0/3实现链路聚合,这2个交换机通过静态路由实现2个不同网段V10、V20互通。 三、华…

为什么工作很卖力,最后还晋升不了?

作者 | 军哥来源 | 军哥手记&#xff08;公众号ID&#xff1a;zxhy_cj&#xff09;最近写文章多了一些思考&#xff0c;要写对大家有价值的、有启发、有思考的东西&#xff0c;希望读者朋友们enjoy&#xff01;为什么自我感觉良好&#xff0c;可是晋升没我&#xff1f;感觉老板…

基于Java的数据采集(终结篇)

关于写过关于JAVA采集入库的三篇文章&#xff1a; 基于Java数据采集入库&#xff08;一&#xff09;&#xff1a;http://www.cnblogs.com/lichenwei/p/3904715.html 基于Java数据采集入库&#xff08;二&#xff09;&#xff1a;http://www.cnblogs.com/lichenwei/p/3905370.ht…

华为交换机、路由器配置单臂路由实现不同网段通信

一、eNSP模拟器仿真图 二、华为交换机配置 //批量创建VLAN [Huawei]vlan batch 10 20 //PC1电脑与交换机连接端口配置 [Huawei]interface GigabitEthernet0/0/2 [Huawei-GigabitEthernet0/0/2]port link-type access [Huawei-GigabitEthernet0/0/2]port default vlan 10 [Huaw…

IDEA 正式版终于支持中文版和 JDK 直接下载了(太方便了)附介绍视频

IDEA 2020.1 经过了漫长的打磨终于发布正式版了&#xff0c;而这次的版本不止直接支持 Java 14&#xff0c;还带来了两个重量级的功能&#xff0c;官方中文版支持和 JDK 直接下载。 在之前的开发中&#xff0c;当我们需要下载 JDK 时&#xff0c;通常的步骤是这样的&#xff1…

HoughLine变换

对于HoughLine变换&#xff0c;有两种方法&#xff0c;标准霍夫变换&#xff08;SHT&#xff09;用的矩阵是CV_32FC2&#xff0c;用极坐标法记录直线,而累积概率霍夫变换&#xff08;PPHT&#xff09;用的是CV_32FC核心函数&#xff1a;cvCvtColor&#xff0c;cvHoughLines2&am…