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

软件发布|下载排行|最新软件

当前位置:首页IT学院IT技术

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

王磊的博客   2020-04-07 我要评论
在开始之前,我们先来看以下代码会有什么问题? ```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()` ! 什么?你不信。那么来看这张图: ![image.png](https://cdn.nlark.com/yuque/0/2020/png/92791/1585830789098-0b01bd07-af1c-4773-b348-0371a2f92486.png#align=left&display=inline&height=145&name=image.png&originHeight=290&originWidth=606&size=35275&status=done&style=none&width=303) 好吧,那为什么不能这样用呢?总得给我一个敷衍的理由吧? ## 问题一:破坏了程序的完整性 其实是这样的,以文章刚开头的那段代码来说,它的执行结果是: > 子线程开始执行 > > 主线程执行完成  我们发现了一个惊天的大问题,最重要的那段伪代码竟然没执行,如下图所示: ![image.png](https://cdn.nlark.com/yuque/0/2020/png/92791/1585831129379-14e348ea-8dc1-476b-9c05-bc608fc3712b.png#align=left&display=inline&height=206&name=image.png&originHeight=411&originWidth=783&size=52896&status=done&style=none&width=391.5) 可以看出使用 `stop()` 终止线程之后,线程剩余的部分代码会放弃执行,这样会造成严重的且不易被发现的惊天大 Bug,假如没有执行的那段代码是释放系统资源的代码,或者是此程序的主要逻辑处理代码。这就**破坏了程序基本逻辑的完整性,导致意想不到的问题发生**,而且它还很隐秘,不易被发现和修复。 有人说,这还不简单,我加个 `finally` 不就完了吗? 这???杠精哪都有,今年特别多。 行,既然这个说服不了你,咱接着往下看。 ## 问题二:破坏了原子逻辑 我们知道在 Java 中 `synchronized` 属于独占式可重入悲观锁,如果我们使用它修饰代码,妥妥的多线程没问题,但如果碰到 `stop()` 方法就不一定了,直接来看代码吧。 ```java 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; @Override public 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()` 的方法,它的废弃源码如下: ```java /** * Forces the thread to stop executing. *

* If there is a security manager installed, its checkAccess * method is called with this * as its argument. This may result in a * SecurityException being raised (in the current thread). *

* 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 checkPermission method (with a * RuntimePermission("stopThread") argument) is called in * addition. * Again, this may result in throwing a * SecurityException (in the current thread). *

* The thread represented by this thread is forced to stop whatever * it is doing abnormally and to throw a newly created * ThreadDeath object as an exception. *

* It is permitted to stop a thread that has not yet been started. * If the thread is eventually started, it immediately terminates. *

* An application should not normally try to catch * ThreadDeath unless it must do some extraordinary * cleanup operation (note that the throwing of * ThreadDeath causes finally clauses of * try statements to be executed before the thread * officially dies). If a catch clause catches a * ThreadDeath object, it is important to rethrow the * object so that the thread actually dies. *

* 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 * ThreadDeath. * * @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 * ThreadDeath 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 stop 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 interrupt method should be used to * interrupt the wait. * For more information, see * Why * are Thread.stop, Thread.suspend and Thread.resume Deprecated?. */ @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 states stop0(new ThreadDeath()); } ``` 可以看出 `stop()` 方法被 `@Deprecated` 注释修饰了,而被此注解修饰的代码表示为过时方法,不建议被使用。从 `stop()` 的备注信息可以看出,官方也不建议使用 `stop()` ,说它是一个非安全的方法。 ## 正确终止线程 那如何终止线程呢?这里提供 2 个正确的方法: 1. 设置退出标识退出线程; 1. 使用 `interrupt()` 方法终止线程。 ### 1.自定义退出标识 我们可以自定义一个布尔变量来标识是否需要退出线程,实现代码如下: ```java // 自定义退出标识退出线程 static class FlagThread extends Thread { public volatile boolean exit = false; public void run() { while (!exit) { // 执行正常的业务逻辑 } } } ``` 可以看出我们使用了关键字 `volatile` 对线程进行了修饰,这样就可以保证多线程的执行安全了,在我们需要让线程退出时,只需要把变量 `exit` 赋值为 `true` 就可以了。 ### 2.interrupt 终止线程 当我们使用 `interrupt()` 方法时,以上两个示例的执行结果就正常了,执行代码如下: ```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.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; @Override public 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()` 方法无疑是最适合我们的终止线程的方式。

Copyright 2022 版权所有 软件发布 访问手机版

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 联系我们