中断

前文说过,Java 处理中断的方式是先唤醒进程,再由线程自己通过 Thread.interrupted() 检查中断状态并提前退出(不熟悉的同学可以复习下线程的中断)。抢锁的方法也实现了约定的中断处理逻辑。

接口定义

AQS 中的抢锁方法有两类处理中断的方式:

  • 直接返回中断状态
    • acquireQueued
    • doAcquireShared
  • 检测到中断时抛 InterruptedException
    • doAcquireInterruptibly
    • doAcquireSharedInterruptibly
    • doAcquireNanos
    • doAcquireSharedNanos

实现

返回中断状态和抛异常的实现几乎没有区别,如 acquireQueued 对异常的处理如下:

final boolean acquireQueued(final Node node, int arg) {
    // ..
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                // ...
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    // ...
}

核心的逻辑是通过 parkAndCheckInterrupt 检查状态并记录在 interrupted变量中返回,而 doAcquireInterruptibly 类似,只是检测到中断时不是记录状态,而是直接抛异常:

private void doAcquireInterruptibly(int arg)
    throws InterruptedException {
    // ...
        for (;;) {
            // ...
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    // ...
}

其中的 parkAndCheckInterrupt 方法也只是调用了 Thread.interrupted 来检测中断状态:

private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

如何取消

当抢锁失败时,会调用 cancelAcquire 来取消当前节点,如下:

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        // ...
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

取消的逻辑比较复杂,我们下节单独介绍。