zoukankan      html  css  js  c++  java
  • 如何优雅地停止一个线程?

    线程终止有两种情况:

    1、线程的任务执行完成

    2、线程在执行任务过程中发生异常

    这两者属于线程自行终止,如何让线程 A 把线程 B 终止呢?

    Java 中 Thread 类有一个 stop() 方法,可以终止线程,不过这个方法会让线程直接终止,在执行的任务立即终止,未执行的任务无法反馈,所以 stop() 方法已经不建议使用。

    既然 stop() 方法如此粗暴,不建议使用,我们如何优雅地结束线程呢?

    线程只有从 runnable 状态(可运行/运行状态) 才能进入terminated 状态(终止状态),如果线程处于 blocked、waiting、timed_waiting 状态(休眠状态),就需要通过 Thread 类的 interrupt()  方法,让线程从休眠状态进入 runnable 状态,从而结束线程。

    当线程进入 runnable 状态之后,通过设置一个标识位,线程在合适的时机,检查该标识位,发现符合终止条件,自动退出 run () 方法,线程终止。

    如我们模拟一个系统监控任务线程,代码如下

    package constxiong.concurrency.a007;
    
    /**
     * 模拟系统监控
     * @author ConstXiong
     */
    public class TestSystemMonitor {
        
        public static void main(String[] args) {
            testSystemMonitor();//测试系统监控器
        }
        
        /**
         * 测试系统监控器
         */
        public static void testSystemMonitor() {
            SystemMonitor sm = new SystemMonitor();
            sm.start();
            try {
                //运行 10 秒后停止监控
                Thread.sleep(10 * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("监控任务启动 10 秒后,停止...");
            sm.stop();
        }
        
    }
    
    /**
     * 系统监控器
     * @author ConstXiong
     */
    class SystemMonitor {
        
        private Thread t;
        
        /**
         * 启动一个线程监控系统
         */
        void start() {
            t = new Thread(() -> {
                while (!Thread.currentThread().isInterrupted()) {//判断当前线程是否被打断
                    System.out.println("正在监控系统...");
                    try {
                        Thread.sleep(3 * 1000L);//执行 3 秒
                        System.out.println("任务执行 3 秒");
                        System.out.println("监控的系统正常!");
                    } catch (InterruptedException e) {
                        System.out.println("任务执行被中断...");
                    }
                }
            });
            t.start();
        }
    
        void stop() {
            t.interrupt();
        }
    }

    执行结果 

    正在监控系统...
    任务执行 3 秒
    监控的系统正常!
    正在监控系统...
    任务执行 3 秒
    监控的系统正常!
    正在监控系统...
    任务执行 3 秒
    监控的系统正常!
    正在监控系统...
    监控任务启动 10 秒后,停止...
    任务执行被中断...
    正在监控系统...
    任务执行 3 秒
    监控的系统正常!
    正在监控系统...
    .
    .
    .

    从代码和执行结果我们可以看出,系统监控器 start() 方法会创建一个线程执行监控系统的任务,每个任务查询系统情况需要 3 秒钟,在监控 10 秒钟后,主线程向监控器发出停止指令。

    但是结果却不是我们期待的,10 秒后并没有终止了监控器,任务还在执行

    原因在于,t.interrupt() 方法让处在休眠状态的语句 Thread.sleep(3 * 1000L); 抛出异常,同时被捕获,此时 JVM 的异常处理会清除线程的中断状态,导致任务一直在执行。

    处理办法是,在捕获异常后,继续重新设置中断状态,代码如下

    package constxiong.concurrency.a007;
    
    /**
     * 模拟系统监控
     * @author ConstXiong
     */
    public class TestSystemMonitor {
        
        public static void main(String[] args) {
            testSystemMonitor();//测试系统监控器
        }
        
        /**
         * 测试系统监控器
         */
        public static void testSystemMonitor() {
            SystemMonitor sm = new SystemMonitor();
            sm.start();
            try {
                //运行 10 秒后停止监控
                Thread.sleep(10 * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("监控任务启动 10 秒后,停止...");
            sm.stop();
        }
        
    }
    
    /**
     * 系统监控器
     * @author ConstXiong
     */
    class SystemMonitor {
        
        private Thread t;
        
        /**
         * 启动一个线程监控系统
         */
        void start() {
            t = new Thread(() -> {
                while (!Thread.currentThread().isInterrupted()) {//判断当前线程是否被打断
                    System.out.println("正在监控系统...");
                    try {
                        Thread.sleep(3 * 1000L);//执行 3 秒
                        System.out.println("任务执行 3 秒");
                        System.out.println("监控的系统正常!");
                    } catch (InterruptedException e) {
                        System.out.println("任务执行被中断...");
                        Thread.currentThread().interrupt();//重新设置线程为中断状态
                    }
                }
            });
            t.start();
        }
    
        void stop() {
            t.interrupt();
        }
    }

    执行结果如预期

    正在监控系统...
    任务执行 3 秒
    监控的系统正常!
    正在监控系统...
    任务执行 3 秒
    监控的系统正常!
    正在监控系统...
    任务执行 3 秒
    监控的系统正常!
    正在监控系统...
    监控任务启动 10 秒后,停止...
    任务执行被中断...

    到这里还没有结束,我们用 Thread.sleep(3 * 1000L); 去模拟任务的执行,在实际情况中,一般是调用其他服务的代码,如果出现其他异常情况没有成功设置线程的中断状态,线程将一直执行下去,显然风险很高。所以,需要用一个线程终止的标识来代替 Thread.currentThread().isInterrupted()。

    修改代码如下

    package constxiong.concurrency.a007;
    
    /**
     * 模拟系统监控
     * @author ConstXiong
     */
    public class TestSystemMonitor {
        
        public static void main(String[] args) {
            testSystemMonitor();//测试系统监控器
        }
        
        /**
         * 测试系统监控器
         */
        public static void testSystemMonitor() {
            SystemMonitor sm = new SystemMonitor();
            sm.start();
            try {
                //运行 10 秒后停止监控
                Thread.sleep(10 * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("监控任务启动 10 秒后,停止...");
            sm.stop();
        }
        
    }
    
    /**
     * 系统监控器
     * @author ConstXiong
     */
    class SystemMonitor {
        
        private Thread t;
        
        private volatile boolean stop = false;
        
        /**
         * 启动一个线程监控系统
         */
        void start() {
            t = new Thread(() -> {
                while (!stop) {//判断当前线程是否被打断
                    System.out.println("正在监控系统...");
                    try {
                        Thread.sleep(3 * 1000L);//执行 3 秒
                        System.out.println("任务执行 3 秒");
                        System.out.println("监控的系统正常!");
                    } catch (InterruptedException e) {
                        System.out.println("任务执行被中断...");
                        Thread.currentThread().interrupt();//重新设置线程为中断状态
                    }
                }
            });
            t.start();
        }
    
        void stop() {
            stop = true;
            t.interrupt();
        }
    }

    执行结果

    正在监控系统...
    任务执行 3 秒
    监控的系统正常!
    正在监控系统...
    任务执行 3 秒
    监控的系统正常!
    正在监控系统...
    任务执行 3 秒
    监控的系统正常!
    正在监控系统...
    监控任务启动 10 秒后,停止...
    任务执行被中断...

    到这里基本算是优雅地让线程终止了。

    使用 volatile 修饰 stop 变量有必要吗?作用是什么?

    线程只能通过 runnable 状态到 terminated 状态,那线程状态是如何变化的呢?

    我们后续继续实践。


     来一道刷了进BAT的面试题?

  • 相关阅读:
    基本技能训练之线程
    关于UEditor的使用配置(图片上传配置)
    PAT 乙级练习题1002. 写出这个数 (20)
    codeforces 682C Alyona and the Tree DFS
    codeforces 681D Gifts by the List dfs+构造
    codeforces 678E Another Sith Tournament 概率dp
    codeforces 680E Bear and Square Grid 巧妙暴力
    codeforces 678D Iterated Linear Function 矩阵快速幂
    codeforces 679A Bear and Prime 100 交互
    XTUOJ 1248 TC or CF 搜索
  • 原文地址:https://www.cnblogs.com/ConstXiong/p/11684697.html
Copyright © 2011-2022 走看看