Thread的interrupt()方法排雷

有些时候会需要在线程的run()方法返回之前手动中止线程,这时就会用到Thread的interrupt()实例方法。这个方法的使用有许多坑,稍不注意就会出错。本文将对这个方法以及与它相关的知识点进行一下梳理。

interrupt()与isInterrupted()

非常常用的两个方法。顾名思义,它们是分别用于中断线程以及检查线程的中断状态的。说实话,正是因为它们的名字取得太简单了,用之前很少有人会去仔细看它的说明文档,这就导致了一大堆的问题。事实上,一看文档就会发现这两个方法里面隐藏了不少的坑。
下面是interrupt()方法的文档的一部分:

     * <p> If this thread is blocked in an invocation of the {@link
     * Object#wait() wait()}, {@link Object#wait(long) wait(long)}, or {@link
     * Object#wait(long, int) wait(long, int)} methods of the {@link Object}
     * class, or of the {@link #join()}, {@link #join(long)}, {@link
     * #join(long, int)}, {@link #sleep(long)}, or {@link #sleep(long, int)},
     * methods of this class, then its interrupt status will be cleared and it
     * will receive an {@link InterruptedException}.

翻译一下就是,如果线程由于调用Object类的wait()方法、Thread类的join()实例方法以及Thread.sleep()静态方法而被挂起,此时其他线程调用了这个线程的interrupt()方法,那么这个线程的中断状态会被清除,并会抛出一个InterruptedException异常。
从上面加粗的文字中可以得出两点结论:
(1)即便你调用了interrupt()方法,你在之后用isInterrupted()方法检查它的中断状态时也不一定能得到true。
(2)如果线程当前运行处的代码块不对InterruptedException异常进行合适的处理,那么interrupt()方法就没有任何效果。
下面举一个例子来验证上面的结论:

public abstract class StoppableThread extends Thread {

    @Override
    public void run() {
        while(!isInterrupted()){
            System.out.println("isInterrupted: " + isInterrupted());
            doWork();
        }
        System.out.println("END");
    }

    protected abstract void doWork();

    public static void main(String[] args) {
        StoppableThread thread = new StoppableThread() {

            @Override
            protected void doWork() {
                System.out.println("running...");
                try {
                    sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        thread.start();

        try {
            Thread.sleep(2200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("即将调用interrupt()方法");
        thread.interrupt();
    }
}

这个例子看上去好像没任何问题,它在run方法中设置了一个while循环不断检查isInterrupted()的返回值,返回值为false则继续,否则跳出循环。
下面是它的运行输出结果:

isInterrupted: false
running...
isInterrupted: false
running...
isInterrupted: false
running...
isInterrupted: false
running...
isInterrupted: false
running...
即将调用interrupt()方法
isInterrupted: false
running...
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at readerandwriter.StoppableThread$1.doWork(StoppableThread.java:22)
    at readerandwriter.StoppableThread.run(StoppableThread.java:9)
isInterrupted: false
running...
isInterrupted: false
running...

结果很匪夷所思吧,主线程调用子线程的interrupt()方法后,非但线程没有停止,而且isInterrupted()方法居然还是返回false。这就是上面的结论(1)和结论(2)组合起来造成的后果。
可能有人会问,既然interrupt()这么不可靠,那干脆就不用这个方法了,靠别的方法来控制循环的出入不就好了吗?答案是不行的,因为如果线程处于挂起状态(wait()、sleep()等),那么常规方法是没办法让它醒过来的,自然也就退出不了。这种情况下就必须要依赖抛出的InterruptedException。

InterruptedException

单看名字很容易认为这个异常是在调用了interrupt()方法后抛出的。事实上,interrupt()方法所做的仅仅是设置了一下Thread对象的中断状态而已,InterruptedException是在线程的wait()、sleep()、join()等方法中抛出的。
下面来验证一下。把上面的那个类的doWork()方法改一改,让它声明抛出InterruptedException,并把实现中的sleep(500)去掉。

public abstract class StoppableThread extends Thread {

    @Override
    public void run() {
        while(!isInterrupted()){
            System.out.println("isInterrupted: " + isInterrupted());
            try {
                doWork();
            } catch (InterruptedException e) {
                System.out.println("InterruptedException is Thrown");
            }
        }
        System.out.println("END");
    }

    protected abstract void doWork() throws InterruptedException;

    public static void main(String[] args) {
        StoppableThread thread = new StoppableThread() {

            @Override
            protected void doWork() throws InterruptedException{
                System.out.println("running...");
            }
        };
        thread.start();

        try {
            Thread.sleep(2200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("即将调用interrupt()方法");
        thread.interrupt();
    }
}

输出结果如下:

isInterrupted: false
running...
isInterrupted: false
running...
isInterrupted: false
running...
isInterrupted: false
running...
isInterrupted: false
running...
即将调用interrupt()方法
isInterrupted: false
running...
END

可以看到,虽然线程结束了,但是InterruptedException异常并没有被抛出(如果抛出了会输出一行InterruptedException is Thrown)。线程是因为isInterrupted()返回true才退出循环的。结合上面interrupt()方法的文档,可以得出两条结论:
(1)interrupt()仅仅设置了Thread对象的中断状态,InterruptedException是在线程的wait()、sleep()、join()等方法中抛出的,并且抛出后会清除掉Thread对象的中断状态。
(2)当线程正在执行一些常规方法时,即便调用了interrupt()方法也不会抛出InterruptedException,但会导致isInterrupted()方法返回true。

Thread.interrupted()

Thread还有一个不太常用的静态方法interrupted(),用来检查当前线程的中断状态的。需要注意的是,这个方法除了返回当前线程的中断状态(true/false),还会把中断状态给清除掉。也就是说,如果你连着调用两次这个方法,那么第二次必然返回的是false,因为无论中断状态原本是什么,它都已经被第一次的Thread.interrupted()清除了。

如何安全可靠地中断线程

那么怎样才能安全可靠地中断线程呢?事实上,似乎没有一个完美的方法能够实现在调用某个方法的瞬间就让线程退出。一个比较好的方法是在Thread子类中添加一个标志位,这里命名为isShutdownRequested,默认为false,并提供一个方法,用于在interrupt()的同时设置它为true。最后,在线程的run()方法的while循环控制条件中利用这个标志位判断线程是否被要求中止。
下面提供了一个StoppableThread实现,里面包含一个shutdown()方法,用于中止线程:

public abstract class StoppableThread extends Thread {
    private volatile boolean isShutdownRequested = false;

    public final void shutdown(){
        isShutdownRequested = true;
        interrupt();
    }

    public final boolean isShutdownRequested() {
        return isShutdownRequested;
    }

    @Override
    public void run() {
        try {
            while(!isShutdownRequested()){
                doWork();
            }
        } catch (InterruptedException e) {
            System.out.println("InterruptedException is Thrown");
        } finally {
            doShutdown();
        }
    }

    protected abstract void doWork() throws InterruptedException;

    protected abstract void doShutdown();


    public static void main(String[] args) {
        StoppableThread thread = new StoppableThread() {

            @Override
            protected void doWork() throws InterruptedException {
                System.out.println("running...");
                sleep(500);
            }

            @Override
            protected void doShutdown() {
                System.out.println("shutdown!");
            }
        };
        thread.start();

        try {
            Thread.sleep(2200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("即将调用shutdown()方法");
        thread.shutdown();
    }
}

下面是输出结果:

running...
running...
running...
running...
running...
即将调用shutdown()方法
InterruptedException is Thrown
shutdown!

可以看到,线程通过InterruptedException成功地退出了。
使用上面这个类时,需要注意以下几点:
(1)需要实现doWork()(每次循环都会调用)与doShutdown()(线程退出前调用)方法。
(2)如果doWork()是一个耗时长的常规方法,那么调用shutdown()后不会马上退出,而会等待这个方法返回。
(3)不能在doWork()方法中处理掉InterruptException,否则可能导致线程退出不了。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值