前言

本文隶属于专栏《100个问题搞定Java并发》,该专栏为笔者原创,引用请注明来源,不足和错误之处请在评论区帮忙指出,谢谢!

本专栏目录结构和参考文献请见100个问题搞定Java并发

正文

在Java中,线程中断是一种重要的线程协作机制。从表面上理解,中断就是让目标线程停止执行的意思,实际上并非完全如此。

在专栏前面我们提到了 stop() 方法停止线程的坏处,详情请见——Java中有哪些方法可以终止线程运行?

在 JDK 中是否有提供完善的支持线程退出的能力呢?答案是肯定的,那就是线程中断。

严格地讲,线程中断并不会使线程立即退出,而是给线程发送一个通知,告知目标线程,有人希望你退出啦!

至于目标线程接到通知后如何处理,则完全由目标线程自行决定这点很重要,如果中断后,线程立即无条件退出,我们就又会遇到 stop 方法的老问题。

有三个方法与线程中断有关,这三个方法看起来很像,可能会引起混淆和误用,希望大家注意。

这三个方法都来自于 java.lang.Thread

public void interrupt(); //中断线程
public boolean isInterrupted(); //判断当前线程是否已经被中断
public static boolean interrupted(); //判断当前线程是否已经被中断,并且清除当前中断状态

Thread.interrupt() 方法是一个实例方法。 它通知目标线程中断,也就是设置中断标志位。 中断标志位表示当前线程已经被中断了。

Thread.isInterrupted() 方法也是实例方法,它判断当前线程是否被中断(通过检査中断标志位)。

最后的静态方法 Thread.interrupted() 也可用来判断当前线程的中断状态,但同时会清除当前线程的中断标志位状态。

Thread.currentThread.interrupt();

package com.shockang.study.java.concurrent.thread.interrupt;

/**
 * sleep中断后 抛出异常被重置中断状态
 * 如果希望sleep后可以判断中断状态,则必须在sleep的异常处理中,在设置中断
 *
 * @author Administrator
 */
public class InterruptSleepThread {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread() {
            @Override
            public void run() {
                while (true) {
                    if (Thread.currentThread().isInterrupted()) {
                        System.out.println("Interrupted!");
                        break;
                    }
                    try {
                        Thread.sleep(2000);
                        // 推荐使用 TimeUnit.MILLISECONDS.sleep(2000);
                    } catch (InterruptedException e) {
                        System.out.println("Interrupted When Sleep");
                        //设置中断状态
                        Thread.currentThread().interrupt();
                    }
                    Thread.yield();
                }
            }
        };
        t1.start();
        Thread.sleep(2000);
        t1.interrupt();
    }
}

在 catch 子句部分,由于已经捕获了中断,我们可以立即退出线程。

但在这里,我们并没有这么做,因为也许在这段代码中,我们还必须进行后续的处理来保证数据的一致性和完整性,

因此,执行了 Thread.interrupt 方法再次中断自己,置上中断标记位。

只有这么做,在前面的中断检査中,才能发现当前线程已经被中断了。

注意: Thread.sleep() 方法由于中断而抛出异常,此时,它会清除中断标记,如果不加处理,那么在下一次循环开始时,就无法捕获这个中断,故在异常处理中,再次设置中断标记位。

java.lang.Thread 源码解析 (JDK8)

/**
 * 中断此线程。 
 * 
 * 除非当前线程正在中断自身(这是始终允许的),否则将调用此线程的checkAccess方法,这可能会导致抛出SecurityException。 
 * 
 * 如果此线程在调用对象类的 wait()、wait(long) 或wait(long, int) 方法时被阻塞,
 * 
 * 或者在调用此类的join()、join(long)、join(long, int)、sleep(long) 或sleep(long, int)方法时被阻塞,
 * 
 * 则其中断状态将被清除,并且它将接收到InterruptedException。
 * 
 * 如果此线程在I/O操作中被阻塞在中断通道上,则通道将被关闭,线程的中断状态将被设置,并且线程将接收java.nio.channels.ClosedByInterruptException。 
 * 
 * 如果该线程在java.nio.channels.Selector中被阻塞,那么该线程的中断状态将被设置,并且它将立即从选择操作返回,可能返回一个非零值,就像调用了选择器的唤醒方法一样。 
 * 
 * 如果前面的条件都不成立,那么这个线程的中断状态将被设置。 
 * 
 * 中断一个非活动线程不需要有任何效果。 
 * 
 * @throws SecurityException–如果当前线程无法修改此线程
 * 
 */
public void interrupt() {
    if (this != Thread.currentThread())
        checkAccess();

    synchronized (blockerLock) {
        Interruptible b = blocker;
        if (b != null) {
            interrupt0();           // Just to set the interrupt flag
            b.interrupt(this);
            return;
        }
    }
    interrupt0();
}
/**
 * 测试此线程是否已中断。
 * 
 * 线程的中断状态不受此方法的影响。 
 * 
 * 由于线程在中断时不活动而被忽略的线程中断将由返回false的方法反映。 
 * 
 * @return 如果此线程已中断,则为true;否则为假。
 */
public boolean isInterrupted() {
    return isInterrupted(false);
}

/**
 * 测试某个线程是否被中断。中断状态是否重置取决于传递的ClearInterrupted的值
 */
private native boolean isInterrupted(boolean ClearInterrupted);
/**
 * 测试当前线程是否已中断。
 * 
 * 此方法清除线程的中断状态。
 * 
 * 换句话说,如果这个方法被连续调用两次,那么第二个调用将返回false
 * 
 * (除非当前线程在第一个调用清除其中断状态之后,第二个调用检查它之前再次中断)。
 * 
 * 由于线程在中断时不活跃而被忽略的线程中断,将由这个方法返回false
 * 
 * @return true 当前线程成功被中断
 *         false 当前线程未被中断
 */
public static boolean interrupted() {
    return currentThread().isInterrupted(true);
}
上一篇 下一篇