Loading [MathJax]/jax/input/TeX/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >震惊!这样终止线程,竟然会导致服务宕机?

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

原创
作者头像
磊哥
修改于 2020-04-07 04:12:13
修改于 2020-04-07 04:12:13
5140
举报
文章被收录于专栏:王磊的博客王磊的博客

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

代码语言:txt
AI代码解释
复制
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

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

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

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

子线程开始执行主线程执行完成 

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

image.png
image.png

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

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

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

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

问题二:破坏了原子逻辑

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

代码语言:txt
AI代码解释
复制
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=1Thread-4 | num=1Thread-2 | num=1Thread-1 | num=1Thread-8 | num=1Thread-6 | num=1Thread-9 | num=1Thread-3 | num=1Thread-7 | num=1Thread-10 | num=1

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

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

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

代码语言:txt
AI代码解释
复制
/**
 * 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 states
    stop0(new ThreadDeath());
}

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

正确终止线程

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

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

1.自定义退出标识

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

代码语言:txt
AI代码解释
复制
// 自定义退出标识退出线程
static class FlagThread extends Thread {
    public volatile boolean exit = false;

    public void run() {
        while (!exit) {
            // 执行正常的业务逻辑
        }
    }
}

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

2.interrupt 终止线程

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

代码语言:txt
AI代码解释
复制
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() 方法无疑是最适合我们的终止线程的方式。

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

Java中文社群公众号二维码.jpg
Java中文社群公众号二维码.jpg

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
安全地终止线程
线程执行完后,将会终止。那么线程除了正常终止外,还有没有别的方式可以终止线程呢?
黑洞代码
2021/01/14
8050
安全地终止线程
宕机了,Redis 如何避免数据丢失?
停止一个线程意味着在任务处理完任务之前停掉正在做的操作,也就是放弃当前的操作。停止一个线程可以用Thread.stop()方法,但最好不要用它。虽然它确实可以停止一个正在运行的线程,但是这个方法是不安全的,而且是已被废弃的方法。在java中有以下3种方法可以终止正在运行的线程:
一行Java
2023/02/23
9120
宕机了,Redis 如何避免数据丢失?
如何停止一个正在运行的线程?
停止一个线程意味着在任务处理完任务之前停掉正在做的操作,也就是放弃当前的操作。停止一个线程可以用Thread.stop()方法,但最好不要用它。
Java技术栈
2020/02/26
2K0
【Java】已解决:`java.lang.ThreadDeath: 线程终止`
java.lang.ThreadDeath是一个特殊的错误类型,继承自java.lang.Error,并且是唯一一个被Thread.stop()方法抛出的异常。尽管ThreadDeath本质上是一个错误而非异常,但它可以被捕获。然而,由于其与线程强制终止密切相关,通常建议不要捕获它,而是允许线程自然终止。
屿小夏
2024/08/27
1550
1.7停止线程
线程停止:在线程处理完任务之前,停掉正在做的操作,也就是放弃当前操作。 在java中有三种方法可以实现线程的停止: 使用退出标志,使线程正常退出,也就是当run方法执行完后线程终止。 使用stop强行
用户1134788
2017/12/27
1.9K0
1.7停止线程
Java多线程的中断机制
这篇文章主要记录使用 interrupt() 方法中断线程,以及如何对InterruptedException进行处理。感觉对InterruptedException异常进行处理是一件谨慎且有技巧的活儿。
Vincent-yuan
2021/08/10
8850
还不知道如何在java中终止一个线程?快来,一文给你揭秘
工作中我们经常会用到线程,一般情况下我们让线程执行就完事了,那么你们有没有想过如何去终止一个正在运行的线程呢?
程序那些事
2023/03/09
4550
深读 JDK 源码丨Java Thread
线程是系统资源分配的最小单位,它被包含在进程之中,是进程中的实际运作单位。JVM 允许应用程序同时运行、执行多个线程,每个线程都有优先权,具有较高优先级的线程优先于优先级较低的线程执行
码脑
2019/07/20
6470
万字长文,Thread 类源码解析!
金九银十,很多小伙伴都打算跳槽。而多线程是面试必问的,给大家分享下 Thread 源码解析,也算是我自己的笔记整理、思维复盘。学习的同时,顺便留下点什么~
JavaFish
2020/09/14
1.4K0
万字长文,Thread 类源码解析!
Java 线程基础
简言之,进程可视为一个正在运行的程序。它是系统运行程序的基本单位,因此进程是动态的。进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动。进程是操作系统进行资源分配的基本单位。
静默虚空
2019/12/26
4890
Java 线程基础
Java线程基础操作
进程和线程的概念 进程 比较好理解,打开Windows 的任务管理器进程页里的一个个 exe 就可以理解为一个进程。 线程可以理解是在进程中独立运行的子任务 ,具体见百度百科 https://baike.baidu.com/item/%E7%BA%BF%E7%A8%8B 使用线程 创建一个线程主要有两种方式 继承 Thread 类 实现 Runnable 接口 Note: Thread 类其实是实现了 Runnable 接口的。使用继承 Thread 的方式创建线程时,最大的局限就是不支持多继承,
佛系编码
2018/07/04
4440
Thread实现及方法
如果在构造Thread的时候没有显示地指定一个ThreadGroup,那么子线程将会被加入到父线程所在的线程组里边。
HLee
2021/04/19
8230
Thread实现及方法
Java 多线程(2)---- 线程的控制
在上一篇文章中我们简单的认识了一下线程。包括线程的优先级、如何创建一个线程(通过继承 Thread 类或者通过新建 Runnable 对象并作为参数传入 Thread 的构造方法中)、线程的声明周期状态(新建状态、运行状态(就绪状态、正在运行状态)、等待状态、阻塞状态、结束状态),最后我们看了一下守护线程的概念和其特点。如果你对线程的一些概念还不熟悉,建议先从第一篇文章看起:Java 多线程(1)— 初识线程,当然,大神请无视这句话。 这篇文章我们来看一下 Java 多线程中对线程的控制。
指点
2019/01/18
7140
Java 多线程(2)---- 线程的控制
线程的停止与暂停
  停止一个线程意味着在线程处理完任务之前停掉正在做的操作,也就是放弃当前的操作。虽然看起来简单,但是必须做好正确的防范措施,以便达到预期的效果。停止一个线程可以用Thread.stop(),但最好不要用它。虽然它确实可以停止一个正在运行的线程,但是这个方法是不安全的,而且已经作废的方法。
全栈程序员站长
2022/09/06
5.7K0
线程的停止与暂停
多线程基础知识(全面):创建线程、线程状态如何变化、wait()、notify()、sleep()、停止线程
一直想着抽时间 学习多线程相关知识,目前整理了多线程的基础部分,特在此记录下,并发安全、线程池等后续再补充。
寻求出路的程序媛
2024/05/01
2780
多线程基础知识(全面):创建线程、线程状态如何变化、wait()、notify()、sleep()、停止线程
【Java】多线程初探
 参考书籍:《Java核心技术 卷Ⅰ 》 Java的线程状态 从操作系统的角度看,线程有5种状态:创建, 就绪, 运行, 阻塞, 终止(结束)。如下图所示 而Java定义的线程状态有: 创建(New)
啦啦啦321
2018/03/30
7270
【Java】多线程初探
JAVA之线程中止(三)
PS:上边介绍了三种线程中止的方式,stop(不要用),interrupt(通过抛出异常,方便开发者始终),volatile(标志位,首先业务逻辑可以通过变量才判断可以使用这种方式),下次一起说说内存屏障和CPU缓存。
IT架构圈
2020/02/25
5410
Java多线程学习(二)——Thread类的方法使用
停止线程意味着在线程处理完任务之前停掉正在做的操作,也就是放弃当前操作。有以下三种方法终止正在运行中的线程:
小森啦啦啦
2019/07/14
6870
学多线程的看过来,带你学习多线程中断机制
当我们点击某个杀毒软件的取消按钮来停止查杀病毒时,当我们在控制台敲入quit命令以结束某个后台服务时……都需要通过一个线程去取消另一个线程正在执行的任务。Java没有提供一种安全直接的方法来停止某个线程,但是Java提供了中断机制。
好好学java
2018/09/21
7030
线程的基本操作及原理
result: 4 或 result: 1 ,该结果产生的原因是因为线程乱序执行导致的,解决方法:
DioxideCN
2023/01/09
2770
线程的基本操作及原理
相关推荐
安全地终止线程
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档