震惊!这样终止线程,竟然会导致服务宕机?
来自:Java中文社群
在开始之前,我们先来看以下代码会有什么问题?
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()
!
什么?你不信。那么来看这张图:
好吧,那为什么不能这样用呢?总得给我一个敷衍的理由吧?
问题一:破坏了程序的完整性
其实是这样的,以文章刚开头的那段代码来说,它的执行结果是:
子线程开始执行
主线程执行完成
我们发现了一个惊天的大问题,最重要的那段伪代码竟然没执行,如下图所示:
可以看出使用 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 个正确的方法:
设置退出标识退出线程;
使用
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()
方法无疑是最适合我们的终止线程的方式。
特别推荐一个分享架构+算法的优质内容,还没关注的小伙伴,可以长按关注一下:长按订阅更多精彩▼如有收获,点个在看,诚挚感谢
震惊!这样终止线程,竟然会导致服务宕机?相关推荐
- 记-ItextPDF+freemaker 生成PDF文件---导致服务宕机
摘要:已经上线的项目,出现服务挂掉的情况. 介绍:该服务是专门做打印的,业务需求是生成PDF文件进行页面预览,主要是使用ItextPDF+freemaker技术生成一系列PDF文件,其中生成流程有:解 ...
- Elasticsearch】es 模糊查询导致Elasticsearch服务宕机
本文为博主九师兄(QQ:541711153 欢迎来探讨技术)原创文章,未经允许博主不允许转载. 可以加我问问题,免费解答,有问题可以先私聊我,本人每天都在线,会帮助需要的人. 但是本博主因为某些原因, ...
- neutron服务宕机导致nova无法创建云主机
情况:服务器重启后,创建云主机失败·,查看日志,我们发现是neutron导致nova创建云主机失败,我们查看neutron服务状态,发现neutron中的openvswitch和metadate服务宕 ...
- linux内存不足宕机,记一次linux机器内存占用太多导致的服务宕机
背景 最近我们测试环境部署的一个项目总是不停的宕机,之前也有过,但是最近特别频繁 猜测 可能是因为cup或者内存占用太大导致的服务宕机 执行 1.登录linux服务器 2.top命令 下面是对每一行信 ...
- java服务宕机的问题排查
java服务宕机的问题排查 - 知乎 java服务是否可用 telnet ip port ps -ef | grep java Linux系统杀掉进程 Linux系统在内存不足时,会主动去杀掉一些进程 ...
- 网站攻击软件_佳能遭严重勒索软件攻击,10TB的数据被窃取,大量服务宕机
佳能遭严重勒索软件攻击,10TB的数据被窃取,大量服务宕机 Garmin 遭勒索攻击的风波未平,近日,佳能又遭受了勒索软件攻击,攻击除了让佳能的一些网站宕机外,据说还导致佳能服务器中高达 10TB 的 ...
- 模拟私网问题导致节点宕机无法启动
模拟私网问题导致节点宕机无法启动 目的 分析过程 GI alert日志 os日志 ocssd.log 日志 参考文档 目的 本文章通过模拟私网问题,导致集群节点宕机,来进行日志分析. # ifconf ...
- Redis 3.2.3 crashed by signal: 11 服务宕机问题排查
Redis 3.2.3 crashed by signal: 11 服务宕机问题排查 现象 Redis执行bgsave .bgrewriteaof.全量scan等操作都会出现崩溃 === REDIS ...
- window tomcat 启动后 点击cmd窗口 快速编辑模式导致项目宕机问题
window tomcat cmd启动后 快速编辑模式导致项目宕机问题 新版本的window 操作系统,cmd默认是开启快速编辑模式 tomcat启动后,如果鼠标无意点击到tomcat cmd窗口 c ...
最新文章
- 如何判断服务器遭到***
- php ca 校验,PHP和SSL CA验证 – 操作系统独立
- 这台计算机似乎没有安装操作系统_前沿科技 | 浙江大学科学家联合之江实验室成功研制全球神经元规模最大的类脑计算机...
- 混合运算lcd显示_各界巨头寄予厚望 MicroLED靠什么成为显示屏的未来
- 控件中的Events个人理解。
- 【转】C#数据结构-有限状态机
- 将单向链表按某值划分成左边小、 中间相等、 右边大的形式~迎娶是挺
- python一个函数调用另一个函数的返回值_在另一个函数中使用返回值
- python类中引用数据是通过_重载python类中的[]运算符以引用数字阵列数据内存
- Php与Mysql关系揭秘
- 酷源KYCMS内容网站管理系统
- Mac系统最强虚拟机(支持Big Sur)
- sd卡插入计算机要格式化,内存卡一直提示格式化,内存卡插电脑要格式化-
- uniapp 下载视频到本地
- Oracle官网下载11g历史版本
- 无限法则裸连本地服务器错误,无限法则D3Dinitfailed报错问题解决办法
- MySQL 网站上的 GA 是什么意思?
- unity3d 为什么要烘焙/unity3d 烘焙作用是为了什么【2020】
- 力扣K神图解算法数据结构解析10
- 2021年清华大学电子系985经验贴(一)
热门文章
- flask-WTF和sqlalchemy结合使用并实现前端页面登录(综合使用)
- 第1关:学习-用循环和数组实现输入某年某月某日,判断这一天一年的第几天
- codeforces数学1600day6[CodeForces - 1029C多区间交+枚举,CodeForces 992C[数学公式推导],CodeForces 992B[质因数分解+暴力枚举]]
- 2016 ACM / ICPC Asia dalian Regional Contest 题解(11 / 11)【每日亿题2021 / 2 / 17】
- Codeforces Round #699 (Div. 2) F - AB Tree(贪心、树上DP)超级清晰,良心题解,看不懂来打我 ~
- 【割边缩点】解题报告:POJ - 3694 - Network(Tarjan割边缩点 + LCA + 并查集优化)
- mysql .net core_MySQL官方.NET Core驱动已出,支持EF Core
- Loadrunner 性能测试服务器监控指标
- 什么是PRD、MRD与BRD?
- java之==和equals区别