热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

震惊!这样终止线程,竟然会导致服务宕机?[编程语言教程]

这是我的第46篇原创文章。在开始之前,我们先来看以下代码会有什么问题?publicclassThreadStopExample{publicstaticvoidmain(Strin

震惊!这样终止线程,竟然会导致服务宕机?[编程语言教程]

技术分享图片
这是我的第 46 篇原创文章。
在开始之前,我们先来看以下代码会有什么问题?

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;

        @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() 的方法,它的废弃源码如下:

/**
 * 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 个正确的方法:
设置退出标识退出线程;
使用 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;

        @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() 方法无疑是最适合我们的终止线程的方式。

99%的程序员都在用Lombok,原理竟然这么简单?我也手撸了一个!|建议收藏!!!

别用 Date 了,来试试这几个类吧!

惊呆了,竟然可以用这种方式秒建Redis集群?

关注下方二维码,订阅更多精彩内容
技术分享图片

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

原文:https://blog.51cto.com/13904087/2515076


推荐阅读
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 在开发中,有时候一个业务上要求的原子操作不仅仅包括数据库,还可能涉及外部接口或者消息队列。此时,传统的数据库事务无法满足需求。本文介绍了Java中如何利用java.lang.Runtime.addShutdownHook方法来保证业务线程的完整性。通过添加钩子,在程序退出时触发钩子,可以执行一些操作,如循环检查某个线程的状态,直到业务线程正常退出,再结束钩子程序。例子程序展示了如何利用钩子来保证业务线程的完整性。 ... [详细]
  • 微软头条实习生分享深度学习自学指南
    本文介绍了一位微软头条实习生自学深度学习的经验分享,包括学习资源推荐、重要基础知识的学习要点等。作者强调了学好Python和数学基础的重要性,并提供了一些建议。 ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 本文介绍了Java高并发程序设计中线程安全的概念与synchronized关键字的使用。通过一个计数器的例子,演示了多线程同时对变量进行累加操作时可能出现的问题。最终值会小于预期的原因是因为两个线程同时对变量进行写入时,其中一个线程的结果会覆盖另一个线程的结果。为了解决这个问题,可以使用synchronized关键字来保证线程安全。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • iOS Swift中如何实现自动登录?
    本文介绍了在iOS Swift中如何实现自动登录的方法,包括使用故事板、SWRevealViewController等技术,以及解决用户注销后重新登录自动跳转到主页的问题。 ... [详细]
  • Android系统源码分析Zygote和SystemServer启动过程详解
    本文详细解析了Android系统源码中Zygote和SystemServer的启动过程。首先介绍了系统framework层启动的内容,帮助理解四大组件的启动和管理过程。接着介绍了AMS、PMS等系统服务的作用和调用方式。然后详细分析了Zygote的启动过程,解释了Zygote在Android启动过程中的决定作用。最后通过时序图展示了整个过程。 ... [详细]
  • 本文介绍了brain的意思、读音、翻译、用法、发音、词组、同反义词等内容,以及脑新东方在线英语词典的相关信息。还包括了brain的词汇搭配、形容词和名词的用法,以及与brain相关的短语和词组。此外,还介绍了与brain相关的医学术语和智囊团等相关内容。 ... [详细]
  • 本文介绍了九度OnlineJudge中的1002题目“Grading”的解决方法。该题目要求设计一个公平的评分过程,将每个考题分配给3个独立的专家,如果他们的评分不一致,则需要请一位裁判做出最终决定。文章详细描述了评分规则,并给出了解决该问题的程序。 ... [详细]
  • Whatsthedifferencebetweento_aandto_ary?to_a和to_ary有什么区别? ... [详细]
  • 本文介绍了贝叶斯垃圾邮件分类的机器学习代码,代码来源于https://www.cnblogs.com/huangyc/p/10327209.html,并对代码进行了简介。朴素贝叶斯分类器训练函数包括求p(Ci)和基于词汇表的p(w|Ci)。 ... [详细]
  • ShiftLeft:将静态防护与运行时防护结合的持续性安全防护解决方案
    ShiftLeft公司是一家致力于将应用的静态防护和运行时防护与应用开发自动化工作流相结合以提升软件开发生命周期中的安全性的公司。传统的安全防护方式存在误报率高、人工成本高、耗时长等问题,而ShiftLeft提供的持续性安全防护解决方案能够解决这些问题。通过将下一代静态代码分析与应用开发自动化工作流中涉及的安全工具相结合,ShiftLeft帮助企业实现DevSecOps的安全部分,提供高效、准确的安全能力。 ... [详细]
  • Introduction(简介)Forbeingapowerfulobject-orientedprogramminglanguage,Cisuseda ... [详细]
author-avatar
江自流
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有