如何优雅地停止一个线程

通常情况下,我们不会手动停止一个线程,而是让线程运行结束自然停止。但是在异常情况下依然需要我们提前停止线程,如程序出错重启等。

为什么不强制停止线程?

对于Java而言,最正确的停止线程的方式是使用interrupt。但是interrupt仅仅起到通知被停止线程的作用。而被停止线程可以选择什么时候停止或者不停止。

那么,为什么不强制停止线程呢?

因为如果不了解对方正在做的工作而贸然强制停止线程可能会引发一些数据安全问题,为了避免造成问题就需要给对方一定的时间来整理收尾工作。如线程正在写入一个文件,这时收到终止信号,它可以根据自身业务判断是选择立即停止还是文件写入后停止。

使用interrupt停止线程

我们一旦调用某个线程的interrupt方法,这个线程的中断标记位会被设置为true。每个线程都有这样的标记位,当线程执行时可以定期检查这个标识位,如果标志位被设置为true,就说明程序向终止该线程。

isInterrupted() 若线程被中断,该方法返回值true

interrupted() 若线程被中断,该方法返回值true,随后中断标识位被重置,因此再次调用此方法,返回值false

例子:

public class InterruptedDemo implements Runnable {

    private volatile  int sum=0;

    @Override
    public void run() {
        while(!Thread.currentThread().isInterrupted()){
            System.out.println(++sum);
        }
        System.out.println(Thread.currentThread().getName()+"被中断");
    }

    public static void main(String[] args) throws InterruptedException {
        InterruptedDemo demo=new InterruptedDemo();
        Thread thread=new Thread(demo);
        thread.start();
        Thread.sleep(100);
        thread.interrupt();
    }
}

启动子线程,随即主线程休眠100ms后 中断子线程。

sleep、wait方法能否被中断?

举例说明:sleep不能被线程中断

public class InterruptedDemo implements Runnable {

    private volatile  int sum=0;

    @Override
    public void run() {
        while(!Thread.currentThread().isInterrupted()){
            try {
                System.out.println(Thread.currentThread().getName()+"将被休眠");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(++sum);
        }
        System.out.println(Thread.currentThread().getName()+"被中断");
    }

    public static void main(String[] args) throws InterruptedException {
        InterruptedDemo demo=new InterruptedDemo();
        Thread thread=new Thread(demo);
        thread.start();
        Thread.sleep(100);
//        thread.wait();
        thread.interrupt();
    }
}

输出结果

Thread-0将被休眠
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at juc.InterruptedDemo.run(InterruptedDemo.java:16)
at java.lang.Thread.run(Thread.java:748)
1
Thread-0将被休眠
2
Thread-0将被休眠
3
Thread-0将被休眠

举例说明wait不能被中断

public class InterruptedDemo implements Runnable {

    private volatile  int sum=0;
    private Object obj=new Object();

    @Override
    public void run() {
        synchronized (obj){
            while(!Thread.currentThread().isInterrupted()){
                try {
                    System.out.println(Thread.currentThread().getName()+"将被阻塞");
                    obj.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(++sum);
            }
            System.out.println(Thread.currentThread().getName()+"被中断");
        }

    }

    public static void main(String[] args) throws InterruptedException {
        InterruptedDemo demo=new InterruptedDemo();
        Thread thread=new Thread(demo);
        thread.start();
        Thread.sleep(100);
        thread.interrupt();
    }
}

输出结果:

Thread-0将被阻塞
1
Thread-0将被阻塞
java.lang.InterruptedException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at juc.InterruptedDemo.run(InterruptedDemo.java:18)
at java.lang.Thread.run(Thread.java:748)

(然后就是假死,不再输出内容)

原因说明

sleep、wait方法被打断会抛出InterruptedException异常,同时会清除中断信号,将中断标识位重置为false,因此Thread.currentThread().isInterrupted()一直等于false。

sleep、wait中断处理方式

catch语句中再次中断线程 因为如果线程在休眠期间被中断,那么会自动清除中断信号。如果手动添加中断信号,这个信号依然可以被捕捉到。这样后续执行的方法依然可以检测到中断并做出相应处理。

     ...
       synchronized (obj){
            while(!Thread.currentThread().isInterrupted()){
                try {
                    System.out.println(Thread.currentThread().getName()+"将被阻塞");
                    obj.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
				//再次中断线程
                Thread.currentThread().interrupt();
                }
     ...
为什么stop()、suspend()、resume()方法不能用于停止线程?

首先这些方法已经被标记为@Deprecated。

stop()会直接把线程停止,这样就没有给线程足够的时间处理想要在停止前保存数据的逻辑,会导致出现数据完整性问题。

而suspend()、resume()方法,问题在于如果线程调用suspend(),它并不会释放锁,就开始进入休眠,但此时有可能仍持有锁,容易导致死锁。因为只有线程被resume()调用才会释放锁。

volatile修饰标记位可以停止线程吗?

在线程被阻塞的环境下,使用volatile修饰标记位不能响应中断。下面分别举例在一般场景和线程被阻塞场景下的使用。

一般场景:

public class VolatileDemo2 implements Runnable {

    private volatile Integer num = 0;
    private volatile boolean isCanceled = false;

    public static void main(String[] args) throws InterruptedException {
        VolatileDemo2 demo2 = new VolatileDemo2();
        Thread thread = new Thread(demo2);
        thread.start();
        Thread.sleep(30);
        demo2.isCanceled = true;

    }


    @Override
    public void run() {
        while (num <= 1000000000 && !isCanceled) {
            if (num % 10 == 0) {
                try {
                    System.out.println(num + "是10的倍数。");
                    Thread.sleep(1);

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            num++;
        }

    }


}

启动子线程,随即主线程休眠30ms后,调整标志位isCanceled去停止子线程。

结论:可以终止

线程被阻塞的场景:

public class VolatileDemo2 implements Runnable {

    private volatile Integer num = 0;
    private volatile boolean isCanceled = false;

    public static void main(String[] args) throws InterruptedException {
        VolatileDemo2 demo2 = new VolatileDemo2();
        Thread thread = new Thread(demo2);
        thread.start();
        Thread.sleep(30);
        thread.wait();
        demo2.isCanceled = true;

    }


    @Override
    public void run() {
        while (num <= 1000000000 && !isCanceled) {
            if (num % 10 == 0) {
                try {
                    System.out.println(num + "是10的倍数。");
                    Thread.sleep(1);

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            num++;
        }

    }


}

启动子线程,随即主线程休眠30ms后,阻塞子线程,随后主线程调整标志位isCanceled去尝试停止子线程。

结论:不可以终止子线程

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值