zoukankan      html  css  js  c++  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

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

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

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

    子线程开始执行

    主线程执行完成

    我们发现了一个惊天的大问题,最重要的那段伪代码竟然没执行,如下图所示:
    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;
    
            @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.
     * <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.自定义退出标识

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

    // 自定义退出标识退出线程
    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() 方法无疑是最适合我们的终止线程的方式。

  • 相关阅读:
    置入式广告 场景中并无实际对应物
    文本自动摘要的方法研究
    [翻译]用DataSource控件以外的方法为GridView提供数据
    留个纪念
    新街口
    [翻译]SharePoint2007中创建Forms认证方式的站点
    路不一定是死的
    网站转移小记
    [转载]什么时候使用webservice
    城市周末的夜还是那么美
  • 原文地址:https://www.cnblogs.com/vipstone/p/12648933.html
Copyright © 2011-2022 走看看