深入浅出Java多线程(三)之如何正确停止线程

221 阅读2分钟

这是我参与8月更文挑战的第3天,活动详情查看:8月更文挑战

所谓上山容易下山难,有了前面的抛砖引玉,自然而然就能有一系列的问题。前面的创建线程、以及正确的启动线程方式,接下来面试官便是有头有尾,来~讲一下怎么停止一个线程。

41e59daf4172eefd4f03296c454b040.jpg

停止线程

所谓停止线程,就是让一个正在处理任务的线程,停止继续执行,放弃当前任务的操作。

关于停止线程,应该是用interrupt去通知线程停止,而不是强制停止。注意,这里强调的是通知,即第三方线程发出停止线程的通知,至于是否停止,由被通知线程去决定。

最佳实践:如何正确停止线程

  • 优先抛出

    在run()方法中调用了其他方法,如下在run()方法中调用了reInMethod()方法,子方法(reInMethod()方法)应该优先抛出异常(throw new Interruption()),不能在子方法中内部try/catch 解决异常,否则在run()方法中无法终止线程。

  • 恢复中断

    如若一定要在子方法中try/catch 捕获异常,则需要在catch内恢复中断,使run()方法判断成功,不应屏蔽中断。

   @Override
    public void run() {
        while (true ){
            if (Thread.currentThread().isInterrupted()){
                System.out.println("Interrupted 程序运行结束");
                break;
            }
            reInMethod();
            System.out.println("保存日志");
        }
    }

    private void reInMethod() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            e.printStackTrace();
        }
    }

停止线程的错误方法

  • stop() → 错误的原因在于 调用stop() 方法会释放掉所有的线程(Orcle官网可知),会破坏线程的运行周期 案例:银行转账10笔钱,第二笔的时候调用stop()方法,中间的金额部分调用失败,导致复盘追责困难

    如下代码案例中,连队中,一部分士兵获取到武器,但是调用stop()方法后,致使一个连队的一部分士兵有武器,一部分没有,导致后期追查困难。

   @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("连队"+i+"开始分发武器");
            for (int j = 0; j < 10; j++) {
                System.out.println("士兵"+j+"拿到");
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("连队"+i+"完成分发");
        }
    }

    public static void main(String[] args) {
        Thread thread = new Thread(new StopThread());
        thread.start();
        try {
            Thread.sleep(10);
            thread.stop();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
  • suspend()、resume()方法并不会释放锁,如果没有其他线程及时解锁,将会导致死锁问题。

  • 利用volatile设置boolean标识位作为中断线程

    利用BlockingQueue构造一个阻塞队列,并生成一个volatile的boolean类型标识位,容易导致线程死锁但不会中断线程 是因为 volatile标识位依赖于while的校验,但是 线程的死锁发生在阻塞队列的storage的put方法。 例:

  • 陷入阻塞中,volatile是无法阻断线程的
  • 此例中,生产者的生产速度很快,消费者速度慢
  • 所以阻塞队列满后,生产者会阻塞,等待消费者进一步消费

         //阻塞队列
        BlockingQueue storage;

        public volatile boolean canceled = false;

		@Override
        public void run() {
            int num = 0;
            try {
                while (num<=100000 && !canceled) {
                    if (num % 100 == 0) {
                        System.out.println("num是100的倍数,被放到队列中");
                        storage.put(num);
                    }
                    num++;
                    Thread.sleep(1);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                System.out.println("生产者停止运行");
            }
        }