面试官:如何停止一个正在运行的线程?我又懵了

35 篇文章 11 订阅

上一篇讲了如何等待子线程运行结束,原文https://blog.csdn.net/qq_33591903/article/details/108496110

本篇讲讲如何终止子线程


暴力停止——Stop方法

package com.qcy.testStopThread;

/**
 * @author qcy
 * @create 2020/09/16 09:40:34
 */
public class Main1 {
    static class MyThread extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 100000; i++) {
                System.out.println(i);
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MyThread myThread = new MyThread();
        myThread.start();
        //保证子线程进入运行状态,避免还没运行就被终止
        Thread.sleep(100);
        //暴力停止子线程
        myThread.stop();
    }
}

输出如下:

可以看得出,线程确实被终止了。

但是,这个是一个被废弃掉的方法,Stop方法上的注释如下:

从上面的注释,我们可以得到以下的信息:

  1. Stop方法时被废弃掉的方法,不推荐使用
  2. 使用Stop方法,会一直向上传播ThreadDeath异常,从而使得目标线程解锁所有锁住的监视器,即释放掉所有的对象锁。使得之前被锁住的对象得不到同步的处理,因此可能会造成数据不一致的问题。
  3. 注释中建议我们取代Stop方法,可以增加一个变量。目标线程周期性地检查这个变量。如果变量在某个时间指示线程终止,则目标线程将以有序的方式从run方法中返回。
  4. 当然,如果目标线程长时间进行等待,则可以使用中断的方式来终止线程。

因此,我们打算在下面的方法中,使用变量或中断的方式来终止线程。


周期性检查条件变量

通过指定一个条件变量,外部线程(比如是主线程)可以控制该变量,内部线程(比如子线程)在内部循环检查该变量。为了保证条件变量在内存中的可见性,我们使用volatile修饰它。有关volatile是如何保证可见性的,可以参考我的另外一篇文章多线程之内存可见性

package com.qcy.testStopThread;

/**
 * @author qcy
 * @create 2020/09/16 10:30:30
 */
public class Main2 {
    static class MyThread extends Thread {
        //条件变量
        private volatile boolean stop = false;
        private int i = 0;

        public void finish() {
            stop = true;
        }

        @Override
        public void run() {
            //循环检查条件变量
            while (!stop) {
                //业务代码
                i++;
                System.out.println(i);
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MyThread myThread = new MyThread();
        myThread.start();
        //保证子线程进入运行状态,避免还没运行就被终止
        Thread.sleep(100);
        //修改条件变量为false
        myThread.finish();
    }
}

可以看得出,如果我们的业务要求在每一次循环结束后去检查条件变量,那么以上的写法是没有问题的。

但是,以上方式的即时性不高,如果我们的业务代码有等待操作,比如sleep与wait操作,那么如何在这些操作中就终止线程呢?


条件变量结合中断

package com.qcy.testStopThread;

/**
 * @author qcy
 * @create 2020/09/16 11:51:30
 */
public class Main3 {
    static class MyThread extends Thread {
        //条件变量
        private volatile boolean stop = false;
        private int i = 0;

        public void finish() {
            stop = true;
            this.interrupt();
        }

        @Override
        public void run() {
            //循环检查条件变量
            while (!stop) {
                //业务代码
                i++;
                System.out.println(i);
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    System.out.println("线程在sleep期间被打断了");
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MyThread myThread = new MyThread();
        myThread.start();
        //保证子线程进入运行状态,避免还没运行就被终止
        Thread.sleep(1000);
        //停止子线程
        myThread.finish();
    }
}

输出:

interrupt方法用来设置线程的中断状态,如果目标线程正阻塞于wait、sleep等方法时,首先会清除目前线程的中断状态,然后抛出java.lang.InterruptedException异常。

可以看得出,子线程确实被终止掉了。

能不能使用Thread.isInterrupted()来代替条件变量呢?


使用Thread.isInterrupted()来代替条件变量

我们将代码稍微改造一下,使用Thread.isInterrupted()用来获取目标线程是否处于中断状态。

package com.qcy.testStopThread;

/**
 * @author qcy
 * @create 2020/09/16 14:03:00
 */
public class Main4 {
    static class MyThread extends Thread {
        private int i = 0;

        @Override
        public void run() {
            while (!Thread.currentThread().isInterrupted()) {
                //业务代码
                i++;
                System.out.println(i);
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    System.out.println("线程在sleep期间被打断了");
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MyThread myThread = new MyThread();
        myThread.start();
        //保证子线程进入运行状态,避免还没运行就被终止
        Thread.sleep(1000);
        //停止子线程
        myThread.interrupt();
    }
}

观察输出:

什么情况,打断了为什么还能继续输出?

在上面其实已经说过了,interrupt方法用来设置线程的中断状态,如果目标线程正阻塞于wait、sleep等方法时,首先会清除目前线程的中断状态,然后抛出java.lang.InterruptedException异常。

目标线程正在进行第11次循环,进入了sleep操作中,此时收到了主线程的interrupt操作,目标线程拥有了中断状态,则先清除中断状态,然后抛出异常,下一次循环检测Thread.currentThread().isInterrupted()时,isInterrupted依然返回false,从而继续进行循环操作。

那怎么停止当前线程呢,很简单,处理异常时,再次打断即可,代码如下:

package com.qcy.testStopThread;

/**
 * @author qcy
 * @create 2020/09/16 14:03:00
 */
public class Main4 {
    static class MyThread extends Thread {
        private int i = 0;

        @Override
        public void run() {
            while (!Thread.currentThread().isInterrupted()) {
                //业务代码
                i++;
                System.out.println(i);
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    System.out.println("线程在sleep期间被打断了");
                    e.printStackTrace();
                    //再次打断,设置中断标志,则之后的isInterrupted为true
                    this.interrupt();
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MyThread myThread = new MyThread();
        myThread.start();
        //保证子线程进入运行状态,避免还没运行就被终止
        Thread.sleep(1000);
        //停止子线程
        myThread.interrupt();
    }
}

输出如下:

可以看得到,子线程被终止掉了。


总结

使用条件变量+中断的方式是终止线程比较优雅的方式,具有一定的即时性。中断仅仅是一种协作机制,是需要被中断的线程自己去处理中断的。

  • 6
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SunAlwaysOnline

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值