【Java 多线程并发】 线程中断

Metadata

title: 【Java 多线程并发】 线程中断
date: 2023-07-04 23:52
tags:
  - 行动阶段/完成
  - 主题场景/程序
  - 笔记空间/KnowladgeSpace/ProgramSpace/BasicsSpace
  - 细化主题/Java
categories:
  - Java
keywords:
  - Java
description: 【Java 多线程并发】 线程中断

概述

线程中断

Java中的线程中断是一种线程间的协作模式,通过设置线程的中断标志并不能直接终止该线程的执行,而是被中断的线程根据中断状态自行处理。
通过 Thread#interrupt() 给线程该线程一个标志位

线程阻塞

Java 中提供了一个较为底层的并发工具类:LockSupport,该类中的核心方法有两个:park(Object blocker) 以及 unpark(Thread thred),前者表示阻塞指定线程,后者表示唤醒指定的线程。

中断方法

  • interrupt()是一个动词,表示中断线程。
    • 本质调用native void interrupt0()方法,interrupt的功能都是在这个native方法中实现的
  • interrupted()是一个形容词,用于检查线程的中断位并修改中断位,
    • 本质调用的是native方法isInterrupted,是通过这个native方法实现的功能
  • isInterrupted()方法只是简单的检查,interrupted()在检查的同时还会对中断位进行操作。
    • 本质调用的是native方法isInterrupted

原理

  • Thread对象的native实现里有一个成员代表线程的中断状态
  • Thread对象的native实现里有一个成员代表线程是否可以阻塞的许可permit

Java线程中断与阻塞的区别

对于很多刚接触编程的人来说,对于线程中断和线程阻塞两个概念,经常性是混淆起来用,单纯地认为线程中断与线程阻塞的概念是一致的,都是指线程运行状态的停止。其实这个观点是错误的,两者之前有很大的区别,本文就再最开始先着重介绍下两者之间的区别。

线程中断

在一个线程正常结束之前,如果被强制终止,那么就有可能造成一些比较严重的后果,设想一下如果现在有一个线程持有同步锁,然后在没有释放锁资源的情况下被强制休眠,那么这就造成了其他线程无法访问同步代码块。因此我们可以看到在 Java 中类似 Thread#stop() 方法被标为 @Deprecated。

针对上述情况,我们不能直接将线程给终止掉,但有时又必须将让线程停止运行某些代码,那么此时我们必须有一种机制让线程知道它该停止了。Java 为我们提供了一个比较优雅的做法,即可以通过 Thread#interrupt() 给线程该线程一个标志位,让该线程自己决定该怎么办。

接下来就用代码来演示下 interrupt() 的作用:

public class InterruptDemo {
    static class MyThread implements Runnable {
        @Override
        public void run() {
            for (int i= 0; !Thread.currentThread().isInterrupted() && i < 200000; i++) {
                System.out.println(Thread.currentThread().getName() + ":i = " + i);
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Thread myThread = new Thread(new MyThread());
        myThread.start();
        // 让线程运行一段时间
        Thread.sleep(5);
        myThread.interrupt();
        // 等待 myThread 运行停止
        myThread.join();
        System.out.println("end");
    }
}

以上代码的运行结果如下:

可以看到,当前线程并没有按 for 循环中的结束量 20000 去跑,而是在被中断后,停止了当前了 for 循环。所以我们可以利用 interrupt 配置线程使用,使得线程在一定的位置停止下来。

不过到这里可能会让人产生一些疑惑,因为在这里看起来,当前线程像是被阻塞掉了,其实并不是的,我们可以利用下面这段代码来演示下:

public class InterruptDemo {
    static class MyThread implements Runnable {
        @Override
        public void run() {
            for (int i= 0; i < 200000; i++) {
                System.out.println(Thread.currentThread().getName() + ":i = " + i);
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Thread myThread = new Thread(new MyThread());
        myThread.start();
        // 让线程运行一段时间
        Thread.sleep(5);
        myThread.interrupt();
        // 等待 myThread 运行停止
        myThread.join();
        System.out.println("end");
    }
}

上面这段代码的运行结果如下:

可见,线程一直打印到 20000,执行完毕后推出线程,并没有像我们预料中在某处中断。所以我们可以得出结论:单纯用 interrupt() 中断线程方法并不能停止当前正在运行的线程,需要配合其他方法才能正确停止线程。

了解完中断的基本概念后,线程的中断还有需要其他需要注意的点:

  • 设置线程中断后,线程内调用 wait()、join()、slepp() 方法中的一种,都会抛出 InterruptedException 异常,且中断标志位被清除,重新设置为 false;
  • 当线程被阻塞,比如调用了上述三个方法之一,那么此时调用它的 interrupt() 方法,也会产生一个 InterruptedException 异常。因为没有占有 CPU 的线程是无法给自己设置中断状态位置的;
  • 尝试获取一个内部锁的操作(进入一个 synchronized 块)是不能被中断的,但是 ReentrantLock 支持可中断的获取模式:tryLock(long time, TimeUnit unit);
  • 当代码调用中需要抛出一个 InterruptedException,捕获之后,要么继续往上抛,要么重置中断状态,这是最安全的做法。

线程阻塞

上面讲完了线程中断,它其实只是一个标志位,并不能让线程真正的停止下来,那么接下来就来介绍如何真正让线程停止下来。

对于这个问题,Java 中提供了一个较为底层的并发工具类:LockSupport,该类中的核心方法有两个:park(Object blocker) 以及 unpark(Thread thred),前者表示阻塞指定线程,后者表示唤醒指定的线程。

// java.util.concurrent.locks.LockSupport
public static void park(Object blocker) {
    Thread t = Thread.currentThread();
    setBlocker(t, blocker);
    UNSAFE.park(false, 0L);
    setBlocker(t, null);
}
 
public static void unpark(Thread thread) {
    if (thread != null)
        UNSAFE.unpark(thread);
}

该方法在 Java 的语言层面上比较简单,最终也是去调用 UNSAFE 中的 native 方法。真正涉及到底层的东西需要去理解 JVM 的源码,这里就不做太多的介绍。不过我们可以用一个简单的例子来演示下这两个方法:

public class LockSupportDemo {
    static class MyThread implements Runnable {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "开始执行");
            LockSupport.park();
            System.out.println(Thread.currentThread().getName() + "执行结束");
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new MyThread(), "线程:MyThread");
        thread.start();
        Thread.sleep(100);
        System.out.println(Thread.currentThread().getName() + "主线程执行中");
        LockSupport.unpark(thread);
        System.out.println(Thread.currentThread().getName() + "主线程执行结束");
    }
}

上述代码的执行结果为:

线程:MyThread开始执行
main主线程执行中
线程:MyThread执行结束
main主线程执行结束

可以看到,myThread 线程在开始执行后停止了下来,等到主线程重新调用 LockSupport.unpark(thread) 后才重新开始执行。

线程的中断

下面我们进入本文的正题,开始向西讲解Java中线程的中断及其原理。

Java的中断是一种协作机制,也就是说通过中断并不能直接中断另外一个线程,而需要被中断的线程自己处理中断。

在Java的中断模型中,每个线程都有一个boolean类型的标识,代表着是否有中断请求(该请求可以来自所有线程,包括被中断的线程本身)。例如,当线程t1想中断线程t2,只需要在线程t1中将线程t2对象的中断标识置为true,然后线程2可以选择在合适的时候处理该中断请求,甚至可以不理会该请求,就像这个线程没有被中断一样。

官方一点的表述:

Java中的线程中断是一种线程间的协作模式,通过设置线程的中断标志并不能直接终止该线程的执行,而是被中断的线程根据中断状态自行处理。

中断方法

Thread类中提供了线程中断相关的方法,主要是下面的3方法,他们具体的作用见下面的表格:

方法名 介绍
void interrupt() 中断线程,设置线程的中断位为true
boolean isInterrupted() 检查线程的中断标记位,true-中断状态, false-非中断状态
static boolean interrupted() 静态方法,返回当前线程的中断标记位,同时清除中断标记,改为false。比如当前线程已中断,调用interrupted(),返回true, 同时将当前线程的中断标记位改为false, 再次调用interrupted(),会发现返回false

记忆方法推荐:

interrupt()是一个动词,表示中断线程。
Interrupted()是一个形容词,用于检查线程的中断位并修改中断位,
isInterrupted()方法只是简单的检查,interrupted()在检查的同时还会对中断位进行操作。

void interrupt()

方法原型:

/**
 * 中断此线程。
 * <p>线程可以中断自身,这是允许的。在这种情况下,不用进行安全性验证({@link #checkAccess() checkAccess} 方法检测)
 * <p>若当前线程由于 wait() 方法阻塞,或者由于join()、sleep()方法,然后线程的中断状态将被清除,并且将收到 {@link InterruptedException}。
 * <p>如果线程由于 IO操作({@link java.nio.channels.InterruptibleChannel InterruptibleChannel})阻塞,那么通道 channel 将会关闭,
 * 并且线程的中断状态将被设置,线程将收到一个 {@link java.nio.channels.ClosedByInterruptException} 异常。
 * <p>如果线程由于在 {@link java.nio.channels.Selector} 中而阻塞,那么线程的中断状态将会被设置,它将立即从选择操作中返回。
 *该值可能是一个非零值,就像调用选择器的{@link java.nio.channels.Selector#wakeupakeup}方法一样。
*
* <p>如果上述条件均不成立,则将设置该线程的中断状态。</p>
* <p>中断未运行的线程不必产生任何作用。
* @throws  SecurityException 如果当前线程无法修改此线程
*/
public void interrupt()

很多人看到 interrupt() 方法,认为“中断”线程不就是让线程停止嘛。实际上, interrupt() 方法实现的根本就不是这个效果, interrupt()方法更像是发出一个信号,这个信号会改变线程的一个标识位属性(中断标识),对于这个信号如何进行响应则是无法确定的(可以有不同的处理逻辑)。很多时候调用 interrupt() 方法非但不是为了停止线程,反而是为了让线程继续运行下去。设置线程中断不影响线程的继续执行

interrupt() 方法的作用:设置该线程的中断标志为true并立即返回(该线程并不一定是当前线程,而是指调用该方法的Thread实例所代表的线程),但线程实际上并没有被中断而会继续向下执行会由用户自己决定要不要终止线程以及什么时候终止线程;如果线程因为调用了wait系列函数、join方法或者sleep方法而被阻塞挂起,其他线程调用该线程的interrupt()方法会使该线程抛出InterruptedException异常而返回。

interrupt()是实例方法,是调用该方法的对象所表示的那个线程的interrupt()。

可中断的阻塞

针对线程处于由sleep, wait, join,方法调用产生的阻塞状态时,调用interrupt方法,会抛出异常InterruptedException,同时会清除中断标记位,自动改为false。

LockSupport.park也会相应中断,但是不会抛出异常,也不会清空中断标记。

一般情况下,抛出异常时,会清空Thread的interrupt状态,在编程时需要注意;

不可中断的阻塞

  1. java.io包中的同步Socket I/O
  2. java.io包中的同步I/O
  3. Selector的异步I/O
  4. Lock.lock()方法不会响应中断;Lock.lockInterruptibly()方法则会响应中断并抛出异常,区别在于park()等待被唤醒时lock会继续执行park()来等待锁,而 lockInterruptibly会抛出异常;
  5. sychronized加的锁。synchronized被唤醒后会尝试获取锁,失败则会通过循环继续park()等待,因此实际上是不会被interrupt()中断的;

实践案例

中断sleep、wait、join等方法
private static void test1() throws InterruptedException {
    Thread t1 = new Thread(() -> {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            log.error("error", e);
        }
    }, "t1");
 
    t1.start();
    Thread.sleep(500);
    t1.interrupt();
    Thread.sleep(100);
    log.info(" interrupt status : {}", t1.isInterrupted());
}

结论: 阻塞方法sleep响应中断,抛出InterruptedException,t1线程也就不会再继续向下执行了,同时清除中断标记位为false。

中断LockSupport.park方法
public static void test3() throws InterruptedException {
    Thread t3 = new Thread(() -> {
        log.debug("t3 park.....");
        LockSupport.park();
        log.debug("t3 unpark.....");
        log.debug("interrupt status: [{}]", Thread.currentThread().isInterrupted());
        
        log.debug("t3 第二次 park.....");
        LockSupport.park();
        log.debug("t3 中断位为true, park失效.....");
    }, "t3");
 
    t3.start();
    Thread.sleep(1000);
    t3.interrupt();
}

结论: 阻塞方法park响应中断,即t3被LockSupport.park()阻塞,然后主线程调用t3.interrupt(),park()方法就响应中断,结束阻塞,并且不会抛出异常,t3线程继续向下执行,同时不会清除中断标记位,仍为true。

中断普通方法
private static void test2() throws InterruptedException {
    Thread t2 = new Thread(() -> {
        while (true) {
            boolean isInterrupted = Thread.currentThread().isInterrupted();
            if (isInterrupted) {
                log.info("interrupt status: {}", isInterrupted);
                break;
            }
        }
    }, "t2");
 
    t2.start();
    Thread.sleep(500);
    t2.interrupt();
    Thread.sleep(100);
    log.info(" thread status, {}, interrupt status : {}", t2.getState(), t2.isInterrupted());
}

结论: 打断正常运行的线程,线程要自己决定是否响应中断,在线程执行过程中不会清空中断状态,但是线程结束后,会重置线程的中断状态位。

中断IO相关方法

interrupt方法源码中有一段代码如下:

private volatile Interruptible blocker;
private final Object blockerLock = new Object();
synchronized (blockerLock) {
    Interruptible b = blocker;
    if (b != null) {
        interrupt0();           // Just to set the interrupt flag
        b.interrupt(this);
        return;
    }
}

其中blocker是Thread的成员变量,Thread提供了blockedOn方法可以设置blocker:

void blockedOn(Interruptible b) {
    synchronized (blockerLock) {
        blocker = b;
    }
}

如果一个nio通道实现了InterruptibleChannel接口,就可以响应interrupt()中断,其原理就在InterruptibleChannel接口的抽象实现类AbstractInterruptibleChannel的方法begin()中:

protected final void begin() {
    if (interruptor == null) {
        interruptor = new Interruptible() {
                public void interrupt(Thread target) {
                    synchronized (closeLock) {
                        if (!open)
                            return;
                        open = false;
                        interrupted = target;
                        try {
                            AbstractInterruptibleChannel.this.implCloseChannel();
                        } catch (IOException x) { }
                    }
                }};
    }
    blockedOn(interruptor);//设置当前线程的blocker为interruptor
    Thread me = Thread.currentThread();
    if (me.isInterrupted())
        interruptor.interrupt(me);
}
 
protected final void end(boolean completed)
    throws AsynchronousCloseException
{
    blockedOn(null);//设置当前线程的blocker为null
    Thread interrupted = this.interrupted;
   //如果发生中断,Thread.interrupt方法会调用Interruptible的interrupt方法,
  //设置this.interrupted为当前线程
    if (interrupted != null && interrupted == Thread.currentThread()) {
        interrupted = null;
        throw new ClosedByInterruptException();
    }
    if (!completed && !open)
        throw new AsynchronousCloseException();
}
 
//Class java.nio.channels.Channels.WritableByteChannelImpl
public int write(ByteBuffer src) throws IOException {
    ......    
    try {
        begin();
        out.write(buf, 0, bytesToWrite);
    finally {
        end(bytesToWrite > 0);
    }
    ......
}
 
//Class java.nio.channels.Channels.ReadableByteChannelImpl
public int read(ByteBuffer dst) throws IOException {
    ......    
    try {
        begin();
        bytesRead = in.read(buf, 0, bytesToRead);
    finally {
        end(bytesRead > 0);
    }
    ......
}

以上述代码为例,nio通道的ReadableByteChannel每次执行阻塞方法read()前,都会执行begin(),把Interruptible回调接口注册到当前线程上。当线程中断时,Thread.interrupt()触发回调接口Interruptible关闭io通道,导致read方法返回,最后在finally块中执行end()方法检查中断标记,抛出ClosedByInterruptException;

Selector的实现类似,所以它也可以响应中断:

//java.nio.channels.spi.AbstractSelector
protected final void begin() {
    if (interruptor == null) {
        interruptor = new Interruptible() {
                public void interrupt(Thread ignore) {
                    AbstractSelector.this.wakeup();
                }};
    }
    AbstractInterruptibleChannel.blockedOn(interruptor);
    Thread me = Thread.currentThread();
    if (me.isInterrupted())
        interruptor.interrupt(me);
}
protected final void end() {
AbstractInterruptibleChannel.blockedOn(null);
}
 
//sun.nio.ch.class EPollSelectorImpl
protected int doSelect(long timeout) throws IOException {
    ......
    try {
        begin();
        pollWrapper.poll(timeout);
    } finally {
        end();
    }
    ......
}

boolean isInterrupted()

方法原型:

/**
 * 测试此线程是否已被中断。线程的中断状态不受此方法的影响。
 * 如果中断时,线程并没有存活,那么该方法返回 false。意思就是,如果线程还没有 start 启动,或者已经消亡,那么返回依然是 false.
 * @return  如果该线程已被中断,返回true;否则返回 false
 */
public boolean isInterrupted()

isInterrupted()方法的作用:只判断此线程(此线程指的是调用isInterrupted()方法的Thread实例所代表的线程)是否被中断 ,是则返回true,否则返回false,不清除中断状态。

如果线程还没有 start 启动,或者已经消亡,那么返回依然是 false。中断状态只代表是否有线程调用中断方法,并不代表这个线程是否在运行。

isInterrupted()是实例方法,是调用该方法的对象所表示的那个线程的isInterrupted()。

boolean interrupted()

方法原型:

/**
 * 测试当前线程是否已被中断。
 * 通过此方法可以清除线程的中断状态.
 * 换句话说,如果此方法要连续调用两次,则第二个调用将返回false(除非当前线程在第一个调用清除了它的中断状态之后,且在第二个调用对其进行检查之前再次中断)
 * 如果中断时,线程并没有存活(还未启动),那么该方法返回 false
 * @return   如果该线程已被中断,返回true;否则返回 false
 */
public static boolean interrupted()

interrupted()方法的作用:判断当前线程(注意,这里指的是当前线程,不是调用该方法的Thread实例所代表的线程)是否被中断(检查中断标志),返回一个boolean(当前的中断标志),被中断则返回true,否则返回false。并清除中断状态(将中断标志设置为false)。因为该方法是static方法,内部是获取当前调用线程的中断标志而不是调用interrupted()方法的实例对象的中断标志。

这里有一点需要注意,调用interrupted()方法时,它会先记录下当前的中断标志位,然后才会去清除中断状态,也就是将中断标志位设置为false,最后返回之前记录的中断标志位。也就是说如果在调用interrupted()方法前当前线程已经中断了(中断标志为true),那么第一次调用interrupted()方法返回的就是true,但是第一次调用之后当前线程的中断标志就被interrupted()方法设置为false了,所以第二次再调用interrupted()方法时中断状态已经被清除,将返回一个false。

代码案例

定义一个MyThread类,继承Thread,如下:

public class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("i =" + (i + 1));
        }
    }
}

在main方法中测试:

public class Do {
    public static void main(String[] args ) {
        MyThread thread = new MyThread();
        thread.start();
        thread.interrupt();
        System.out.println("第一次调用thread.isInterrupted():" + thread.isInterrupted());
        System.out.println("第二次调用thread.isInterrupted():" + thread.isInterrupted());
        System.out.println("thread是否存活:" + thread.isAlive());
    }
}

输出如下:

从结果可以看出调用interrupt()方法后,线程仍在继续运行,并未停止,但已经给线程设置了中断标志,两个isInterrupted()方法都会输出true,也说明isInterrupted()方法并不会清除中断状态。

下面我们把代码修改一下,多加两行调用interrupted()方法:

public class Do {
    public static void main(String[] args ) {
        MyThread thread=new MyThread();
        thread.start();
        thread.interrupt();
        System.out.println("第一次调用thread.isInterrupted():"+thread.isInterrupted());
        System.out.println("第二次调用thread.isInterrupted():"+thread.isInterrupted());
 
        //测试interrupted()函数
        System.out.println("第一次调用thread.interrupted():"+thread.interrupted());
        System.out.println("第二次调用thread.interrupted():"+thread.interrupted());
        System.out.println("thread是否存活:"+thread.isAlive());
    }
}

输出如下:

从输出结果看,可能会有疑惑,为什么后面两个interrupted方法输出的都是false,而不是预料中的一个true、一个false?

源码分析

我们会发现这几个Java方法,底层其实是调用native方法实现的,所以线程的中断状态,并不是由 Java 来决定。实际上,Thread 类中并没有维护线程的中断状态。线程中断状态不是 Thread 类的标志位,而是操作系统中对线程的中断标志。

interrupt()方法源码

源码:

public void interrupt() {
    // 除非当前线程是自己中断自己,否则将调用此线程的 checkAccess 方法,这可能导致抛 SecurityException。
    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();
}
 
public final void checkAccess() {
    SecurityManager security = System.getSecurityManager();
    if (security != null) {
        security.checkAccess(this);
    }
}

本质调用的就是这个native方法,interrupt的功能都是在这个native方法中实现的

// 中断线程的底层方法
private native void interrupt0();

interrupt中文是打断的意思,意思是可以打断中止正在运行的线程,比如:

  1. Object#wait()、Thread#join()、Thread#sleep()这些方法运行后,线程的状态是WAITING或TIMED_WAITING,这时候打断这些线程,就会抛出InterruptedException异常,使线程的状态直接到TERMINATED。且中断标志被清除,重新设置为false
  2. 如果线程堵塞在java.nio.channels.InterruptibleChannel的IO上,我们中断当前线程,连接(Channel)会被关闭,线程会被设置为中断状态,并抛出ClosedByInterruptException异常;
  3. 如果线程堵塞在java.nio.channels.Selector上,线程被置为中断状态,select方法会立即返回,就像调用了选择器的 wakeup 方法一样。

如果上面三种情况都没有发生,就会去调用native方法interrupt0(),当前线程的中断标志就会被设置为true。

中断非活动的线程不会有任何的反应。

我们举一个例子来说明如何打断WAITING的线程,代码如下:

public class ThreadInterrupt {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " begin run");
                try {
                    System.out.println("子线程开始沉睡30s");
                    Thread.sleep(30000L);
                } catch (InterruptedException e) {
                    System.out.println("子线程被打断");
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " end run");
            }
        });
        // 开一个子线程去执行
        thread.start();
        Thread.sleep(1000L);
        System.out.println("主线程等待1s后,发现子线程还没有运行成功,打断子线程");
        thread.interrupt();
    }
}

isInterrupted()方法源码

源码

public boolean isInterrupted() {
    // 传递 false 说明不清除中断标志
    return isInterrupted(false);
}

本质调用的是native方法isInterrupted,是通过这个native方法实现的功能

private native boolean isInterrupted(boolean ClearInterrupted);

检测线程是否被中断,是则返回true,否则返回false。

interrupted()方法源码

源码

public static boolean interrupted() {
        // 传递 true 说明清除中断标志
    return currentThread().isInterrupted(true);
}

本质调用的是native方法isInterrupted

/**
 * 测试某些线程是否已被中断。线程的中断状态不受此方法的影响。
 * ClearInterrupted参数决定线程中断状态是否被重置,若为true则重置。
 */
private native boolean isInterrupted(boolean ClearInterrupted);

检测当前线程是否被中断,返回值同上 isInterrupted() ,不同的是,如果发现当前线程被中断,会清除中断标志;换句话说,如果要连续两次调用此方法,则第二个调用将返回false(除非在第一次调用清除了其中断状态之后,在第二次调用对其进行检查之前,当前线程再次被中断)。

该方法是static方法,内部是获取当前调用线程的中断标志而不是调用interrupted()方法的实例对象的中断标志。

interrupt()中断行为研究

原理简单讲解

首先声明,本文不会去贴native方法的cpp实现,而是以伪代码的形式来理解这些native方法。

  • Thread对象的native实现里有一个成员代表线程的中断状态,我们可以认为它是一个bool型的变量。初始为false。
  • Thread对象的native实现里有一个成员代表线程是否可以阻塞的许可permit,我们可以认为它是一个int型的变量,但它的值只能为0或1。当为1时,再累加也会维持1。初始为0。

总结

  • park调用后一定会消耗掉permit,无论unpark操作先做还是后做(看上面对park讲解的源码中,在park开始和结尾都执行了一遍消耗permit的操作,所以无论unpark操作在park操作之前还是之后,只要是调用了park,那么现成的permit最后一定是0)。
  • 如果中断状态为true,那么park无法阻塞。
  • unpark会使得permit为1,并唤醒处于阻塞的线程。
  • interrupt()会使得中断状态为true,并调用unpark。
  • sleep() / wait() / join()调用后一定会消耗掉中断状态,无论interrupt()操作先做还是后做(看上面对sleep讲解的源码中,在sleep开头和结尾都执行了一遍消耗中断状态的代码,所以无论interrupt操作在sleep之前还是之后,只要是调用了sleep,那么线程的中断状态最后一定是false)。

关于这一点,“如果中断状态为true,那么park无法阻塞”。在AQS源码里的acquireQueued里,由于acquireQueued是阻塞式的抢锁,线程可能重复着 阻塞->被唤醒 的过程,所以在这个过程中,如果遇到了中断,一定要用Thread.interrupted()将中断状态消耗掉,并将这个中断状态暂时保存到一个局部变量中去。不然只要遇到中断一次后,线程在抢锁失败后就无法阻塞了。