如何优雅的中断线程

前言

有时候我们需要中断一个线程。由于启动一个线程的方法是start(),所以终止一个线程很容易想到stop(),JDK在
java.lang.Thread类中确实提供了一个stop()方法,这个方法的作用是强制停止一个线程。但是从jdk1.2开始就被标记@Deprecated废弃了,对应废弃的还有suspend()resume(),因为强制停止一个线程不管该线程是否执行完成,并且会释放该线程持有的锁,因此会产生安全问题。

中断线程

一个线程不应该由其它线程来强制中断或停止,而是应该由线程自己停止。

interrupt()方法

每个线程会有一个boolean类型的中断状态来标识该线程是否需要被中断。java.lang.Thread提供了Thread#interrupt()方法来改变中断状态,该方法的作用并不是中断线程,而是通过把中断状态设置为true,来通知线程应该中断。但是是否执行中断,由线程自己决定。这才是中断线程的优雅实现,除了java.lang.Thread提供的interrupt()方法外,还有一种是自定义一个标记变量来标识线程是否应该被终止
调用interrupt()方法时,线程可以处于不同的状态(不是中断状态,如果不清楚线程的状态,可以参考我之前的博客:Java线程到底有几种状态):

  • 如果线程处于调用sleep(...)join(...)wait(...)等方法后的阻塞状态(包括WAITING、TIMED_WAITING状态),那么线程将立即退出被阻塞的状态,清除中断状态(中断状态设置为false),抛出InterruptedException异常。
    public static void main(String[] args) throws Exception {
    
        Thread thread = new Thread(() -> {
            try {
                Thread.sleep(5000);
                System.out.println("thread线程会被终止吗");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        thread.start();
        // 主线程休眠100毫秒,让thread线程启动
        Thread.sleep(100);
        System.out.println("thread状态:" + thread.getState().toString());
        System.out.println(thread.isInterrupted());
        thread.interrupt();
        // 主线程休眠100毫秒,中断状态会被清理
        Thread.sleep(100);
        System.out.println(thread.isInterrupted());
    }
    
    输出结果
    thread状态:TIMED_WAITING
    false
    java.lang.InterruptedException: sleep interrupted
    false
    
    调用interrupt()时,thread线程处于TIMED_WAITING状态,抛出了InterruptedException,并且调用线程的中断状态也清理成了false,并且thread线程的任务也并没有执行完成。当然,调用interrupt()方法前后,thread线程的中断状态都为false,所以有理由怀疑线程的中断状态一直是false,而不是false->true->false。关于这点,可以注释示例倒数第二行的Thread.sleep(100),多运行几次,最后一行输出有可能是true或者false。输出为true表示thread线程的中断状态还没来得及被清理就输出了,由此可以看出中断状态确实是false->true->false。
  • 如果线程处于IO或者NIO中的Channel阻塞状态,IO操作会被中断或者返回特殊异常值。达到终止线程的目的。
  • 如果都不满足,则会设置此线程的中断状态。
    public static void main(String[] args) throws Exception {
    
        Thread thread = new Thread(() -> {
           while (true) { }
        });
        thread.start();
        // 主线程休眠100毫秒,让thread线程启动
        Thread.sleep(500);
        System.out.println("thread状态:" + thread.getState().toString());
        System.out.println(thread.isInterrupted());
        thread.interrupt();
        // 主线程休眠100毫秒,中断状态也不会被清理
        Thread.sleep(100);
        System.out.println(thread.isInterrupted());
    }
    
    输出结果
    thread状态:RUNNABLE
    false
    true
    
    thread线程处于正常运行状态,调用interrupt()方法后仅仅只是设置了中断状态。

java.lang.Thread除了提供interrupt()方法外,还提供了几个相似或者说方法名相似的方法:

  • isInterrupted (不会影响中断状态)
    /**
     * Tests if some Thread has been interrupted.  The interrupted state
     * is reset or not based on the value of ClearInterrupted that is
     * passed.
     * 检测线程是否已经被中断,中断状态是否重置取决于ClearInterrupted参数所传的值
     */
    private native boolean isInterrupted(boolean ClearInterrupted);
    
    /**
     * Tests whether this thread has been interrupted.  The <i>interrupted
     * status</i> of the thread is unaffected by this method.
     * 检测当前线程是否已经被中断,该方法不会影响中断状态(interrupted status)
     * @revised 6.0
     */
    public boolean isInterrupted() {
        return isInterrupted(false);
    }
    
  • interrupted (中断状态会被此方法清理,是不是有点想吐槽这个方法名)
    /**
     * Tests whether the current thread has been interrupted.  The
     * <i>interrupted status</i> of the thread is cleared by this method.  In
     * other words, if this method were to be called twice in succession, the
     * second call would return false (unless the current thread were
     * interrupted again, after the first call had cleared its interrupted
     * status and before the second call had examined it).
     * 检测当前线程是否已经被中断,该线程的中断状态(interrupted status)会被此方法清理。
     * 换句话说,如果这个方法连续两次被调用,第二次调用会返回false
     * (除非线程再次中断,在第一次调用已经清理中断状态之后且在第二次调用之前检测)
     * @revised 6.0
     */
    public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }
    
自定义变量

利用自定义变量来标识线程是否应该被终止,思想和interrput()方法一样,只不过interrput()方法中的中断状态是内置的

private static volatile Boolean interrupt = false;

public static void main(String[] args) throws Exception {

    Thread thread = new Thread(() -> {
        while (!interrupt) {
            // 执行线程本身的逻辑
        }
        // 执行线程中断后的逻辑
    });
    thread.start();
    // 主线程休眠100毫秒,让thread线程启动
    Thread.sleep(100);
    // 中断线程,相当于调用interrupt()方法
    interrupt = true;
}

注:interrupt变量为什么要加volatile关键字,先挖个坑,有空再填。

实践

知道了如何中断线程和线程的中断状态的几个方法,具体到应用应该怎样用呢,应该遵循怎样的原则呢?
如果一个线程有被中断的需求,那么该线程有义务检测自己的状态,在适当的时候执行适当的逻辑。
例如:

public static void main(String[] args) throws Exception {

    Thread thread = new Thread(() -> {
        while (!Thread.currentThread().isInterrupted()) {
            // 执行线程本身的逻辑
        }
        // 执行线程中断后的逻辑
    });
    thread.start();
    // 主线程休眠100毫秒,让thread线程启动
    Thread.sleep(100);
    // 中断线程thread
    thread.interrupt();
}

线程里的任务可能会抛出InterruptException,对于这个异常,线程可以自己处理,也可以向上抛,但是千万不要什么都不做(既不处理异常,也不向上抛)。因为这样调用者根本不知道线程中断了。异常向上抛也需要注意,此时线程的中断状态(interrupt status)可能已经被清除了,也就是interrupt status为false,所以向上抛之前需要调用一次interrupt()方法。

补充

能响应中断事件的方法:

  • Object.wait()/wait(long)/wait(long, int)
  • Thread.sleep(long)/sleep(long, int)
  • Thread.join()/join(long)/join(long, int)
  • java.util.concurrent.BlockingQueue#take()/put(E)
  • java.util.concurrent.locks.Lock#lockInterruptibly()
  • java.util.concurrent.CountDownLatch#awit()
  • java.util.concurrent.CyclicBarrier#await()
  • java.util.concurrent.Exchanger#exchange(V)
  • java.util.concurrent.FutureTask#get()
  • java.nio.channels.InterruptibleChannel()
  • java.nio.channels.Selector的相关方法
发布了52 篇原创文章 · 获赞 107 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/Baisitao_/article/details/99894214