如何正确中断线程?

在线程运行的过程中 如何正确的停止线程? 是否可以使用volatile来停止线程?

使用Interrupt

对于java而言 最正确的停止线程的方式就是Interrupt,但是Interrupt仅仅起到了通知线程的作用 并不会主动的去中断线程
看一下使用方式

while (!Thread.currentThread().isInterrupted() && more work to do) {
    do more work
}

当线程调用interrupt方法之后 这个线程的中断标记位就被置为true 我们可以通过isInterrupted()方法来检测是否中断 并且做出响应

sleep期间是否可以响应到中断

我们在使用中断的过程中 需要思考另一个问题 就是如果我们的线程sleep或者wait了 是否还可以响应中断? 如果不能响应中断 那么响应延后性就太强了

看一下代码

Runnable runnable = () -> {
    int num = 0;
    try {
        while (!Thread.currentThread().isInterrupted() && 
        num <= 1000) {
            System.out.println(num);
            num++;
            Thread.sleep(1000000);
        }
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
};

看一下sleep时是否可以响应interrupt的代码

Runnable runnable = new Runnable() {
            @Override
            public void run() {
                int i = 0;
                while (!Thread.currentThread().isInterrupted() && i < 1000) {
                    i++;
                    System.out.println(i);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        System.out.println("线程中断");
                    }
                }
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
        try {
            Thread.sleep(5000);
            thread.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

我们发现是可以响应到Interrupt中断的 当我们调用interrupt时 线程是可以响应到中断信号的 并且会抛出InterruptedException异常 并且重新将中断信号置为false java在设计之初就考虑到了这种情况 我们应该在catch中对异常做处理或者抛出异常
如果我们在catch中不做任何处理 相当于把中断隐藏了 这是非常不合理的

使用volatile来中断线程

我们可以使用标志位来处理 每次循环都判断一下标志位是否为false 如果为false 则中断线程

public class VolatileCanStop implements Runnable {

    private volatile boolean canceled = false;

    @Override

    public void run() {
        int num = 0;
        try {
            while (!canceled && num <= 1000000) {
                if (num % 10 == 0) {
                    System.out.println(num + "是10的倍数。");
                }
                num++;
                Thread.sleep(1);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
 

    public static void main(String[] args) throws InterruptedException {
        VolatileCanStop r = new VolatileCanStop();
        Thread thread = new Thread(r);
        thread.start();
        Thread.sleep(3000);
        r.canceled = true;
    }
}

但是使用volatile使用过程中 会有一个弊端
比如在线程运行过程中 处于阻塞状态 这时候我们将canceled标志位改了发现并不生效 因为线程目前已经是阻塞状态了 需要先变成运行状态 才可以响应到canceled标志位

以生产者/消费者为例

class Producer implements Runnable {

    public volatile boolean canceled = false;
    BlockingQueue storage;
    public Producer(BlockingQueue storage) {
        this.storage = storage;
    }

    @Override
    public void run() {
        int num = 0;
        try {
            while (num <= 100000 && !canceled) {
                if (num % 50 == 0) {
                    storage.put(num);
                    System.out.println(num + "是50的倍数,被放到仓库中了。");
                }
                num++;
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println("生产者结束运行");
        }
    }
}

我们看到 当仓库满的时候 BlockingQueue会阻塞线程 这时候我们已经没办法响应canceled了 相反 我们可以响应interrupt信号

总结

总的来看 中断线程的方法最合适的只有interrupt方法 或者某些场景下 我们可以使用volatile关键字来实现线程中断

参考

Java 并发编程 78 讲

推荐阅读更多精彩内容