关于java:阅读-JDK-8-源码AQS-中的独占模式

4次阅读

共计 15366 个字符,预计需要花费 39 分钟才能阅读完成。

AbstractQueuedSynchronizer,简称 AQS,是一个用于构建锁和同步器的框架。
JUC 包下常见的锁工具如 ReentrantLock、ReentrantReadWriteLock、Semaphore、CountDownLatch 都是基于 AQS 实现的。
本文将介绍 AQS 的数据结构及独占模式的实现原理。

1. AQS 框架

AQS 所有操作都围绕着同步资源(synchronization state)来开展,解决了资源拜访的互斥和同步问题。

  • 反对独占、共享形式来拜访资源。
  • 对于无奈获取资源的线程,在同步队列中期待,直到获取资源胜利(或超时、中断)并出队。
  • 线程胜利获取资源之后,若指定条件不成立,则开释资源并进入条件队列中期待,直到被唤醒,再转移到同步队列中期待再次获取资源。

AQS 框架将剩下的一个问题留给用户:获取、开释资源的具体形式和后果。
这其实是一种典型的模板办法设计模式:父类(AQS 框架)定义好骨架和外部操作细节,具体规定由子类去实现。

1.1 继承体系

AbstractQueuedSynchronizer 继承 AbstractOwnableSynchronizer,后者具备属性 exclusiveOwnerThread,用于记录独占模式下取得锁的线程。

public abstract class AbstractOwnableSynchronizer implements java.io.Serializable {
    /**
     * The current owner of exclusive mode synchronization.
     */
    private transient Thread exclusiveOwnerThread;
}    

AbstractQueuedSynchronizer 具备 ConditionObject 和 Node 两个外部类。
ConditionObject 是对 Condition 接口的实现,能够与 Lock 配合应用。
Node 是 AQS 中同步队列、条件队列的节点。

1.2 模板办法

AQS 定义了一系列模板办法如下:

// 独占获取(资源数)protected boolean tryAcquire(int arg) {throw new UnsupportedOperationException();
}

// 独占开释(资源数)protected boolean tryRelease(int arg) {throw new UnsupportedOperationException();
}

// 共享获取(资源数)protected int tryAcquireShared(int arg) {throw new UnsupportedOperationException();
}

// 共享获取(资源数)protected boolean tryReleaseShared(int arg) {throw new UnsupportedOperationException();
}

// 是否排它状态
protected boolean isHeldExclusively() {throw new UnsupportedOperationException();
}    

Java 中罕用的锁工具都是基于 AQS 来实现的。

2. 数据结构

2.1 资源定义

锁和资源是同一个概念,是多个线程抢夺的对象。
AQS 应用 state 来示意资源 / 锁,通过内置的期待队列来实现获取资源 / 锁的排队工作。
期待队列(wait queue)是严格的 FIFO 队列,是 CLH 锁队列的变种。

因为 state 是共享的,应用 volatile 来保障其可见性,并提供了 getState/setState/compareAndSetState 三个办法来操作 state。

/**
 * The synchronization state.
 */                          
private volatile int state;// 资源 / 锁

/**
 * Returns the current value of synchronization state.
 * This operation has memory semantics of a {@code volatile} read.
 * @return current state value
 */
protected final int getState() {return state;}

/**
 * Sets the value of synchronization state.
 * This operation has memory semantics of a {@code volatile} write.
 * @param newState the new state value
 */
protected final void setState(int newState) {state = newState;}

/**
 * Atomically sets synchronization state to the given updated
 * value if the current state value equals the expected value.
 * This operation has memory semantics of a {@code volatile} read
 * and write.
 *
 * @param expect the expected value
 * @param update the new value
 * @return {@code true} if successful. False return indicates that the actual
 *         value was not equal to the expected value.
 */
protected final boolean compareAndSetState(int expect, int update) { // 原子操作
    // See below for intrinsics setup to support this
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

2.2 节点定义

AQS 的外部实现了两个队列:同步队列和条件队列。这两种队列都应用了 Node 作为节点。

节点的定义次要蕴含三局部内容:

  1. 节点的状态:SIGNAL、CANCELLED、CONDITION、PROPAGATE、0。
  2. 节点的模式:同步队列中的节点具备两种模式,独占(SHARED)、共享(EXCLUSIVE)。
  3. 节点的指向:同步队列是双向链表(prev/next),条件队列是单向链表(nextWaiter)。

节点的状态

  • CANCELLED:值为 1,示意以后节点因为超时或中断被勾销。
  • SIGNAL:值为 -1,唤醒信号,示意以后节点的后继节点正在期待获取锁。该状态下的节点在 release 或 cancel 时须要执行 unpark 来唤醒后继节点。
  • CONDITION:值为 -2,示意以后节点为条件队列节点,同步队列的节点不会有这个状态。当节点从条件队列转移到同步队列时,状态会初始化为 0。
  • PROPAGATE:值为 -3,只有共享模式下,同步队列的头节点才会设置为该状态(见 doReleaseShared),示意后继节点能够发动获取共享资源的操作。
  • 0:初始状态,示意以后节点在同步队列中,期待获取锁。

java.util.concurrent.locks.AbstractQueuedSynchronizer.Node

static final class Node {
    /** Marker to indicate a node is waiting in shared mode */
    static final Node SHARED = new Node();
    /** Marker to indicate a node is waiting in exclusive mode */
    static final Node EXCLUSIVE = null;

    /** waitStatus value to indicate thread has cancelled */
    static final int CANCELLED =  1;
    /** waitStatus value to indicate successor's thread needs unparking */
    static final int SIGNAL    = -1;
    /** waitStatus value to indicate thread is waiting on condition */
    static final int CONDITION = -2;
    /** waitStatus value to indicate the next acquireShared should unconditionally propagate */ 
    static final int PROPAGATE = -3;

    // 期待状态:SIGNAL、CANCELLED、CONDITION、PROPAGATE、0
    volatile int waitStatus;

    // 指向同步队列中的上一个节点
    volatile Node prev;

    // 指向同步队列中的下一个节点
    volatile Node next;

    volatile Thread thread;

    // 在同步队列中,nextWaiter 用于标记节点的模式:独占、共享
    // 在条件队列中,nextWaiter 指向条件队列中的下一个节点
    Node nextWaiter;

    /**
     * Returns true if node is waiting in shared mode.
     */
    // 节点模式是否为共享
    final boolean isShared() {return nextWaiter == SHARED;}

    /**
     * Returns previous node, or throws NullPointerException if null.
     * Use when predecessor cannot be null.  The null check could
     * be elided, but is present to help the VM.
     *
     * @return the predecessor of this node
     */
    final Node predecessor() throws NullPointerException {
        Node p = prev;
        if (p == null)
            throw new NullPointerException();
        else
            return p;
    }

    Node() {    // Used to establish initial head or SHARED marker}

    Node(Thread thread, Node mode) {     // Used by addWaiter
        this.nextWaiter = mode;          
        this.thread = thread;
    }

    Node(Thread thread, int waitStatus) { // Used by Condition
        this.waitStatus = waitStatus;
        this.thread = thread;
    }
}

2.3 同步队列


同步队列是期待获取锁的队列,是一个双向链表(prev/next),应用 head/tail 执行队列的首尾节点。

java.util.concurrent.locks.AbstractQueuedSynchronizer

/**
 * Head of the wait queue, lazily initialized.  Except for         
 * initialization, it is modified only via method setHead.  Note:
 * If head exists, its waitStatus is guaranteed not to be          
 * CANCELLED.
 */
// 期待队列的头节点,懒初始化。// 留神,如果头节点存在,那么它的 waitStatus 肯定不是 CANCELLED
private transient volatile Node head;

/**
 * Tail of the wait queue, lazily initialized.  Modified only via  
 * method enq to add new wait node.                                
 */
// 期待队列的尾节点,懒初始化。// 只能通过 enq 办法给期待队列增加新的节点。private transient volatile Node tail;

/**
 * The synchronization state.
 */
private volatile int state;

在线程尝试获取资源失败后,会进入同步队列队尾,给前继节点设置一个唤醒信号后,通过 LockSupport.park(this) 让本身进入期待状态,直到被前继节点唤醒。

当线程在同步队列中期待,获取资源胜利后,通过执行 setHead(node) 将本身设为头节点。
同步队列的头节点是一个 dummy node,它的 thread 为空(某些状况下能够看做是代表了以后持有锁的线程)。

/**
 * Sets head of queue to be node, thus dequeuing. Called only by
 * acquire methods.  Also nulls out unused fields for sake of GC
 * and to suppress unnecessary signals and traversals.
 *
 * @param node the node
 */
private void setHead(Node node) {
    head = node;
    node.thread = null;
    node.prev = null;
}

AQS 不会在初始化队列的时候构建空的头节点(dummy node),而是在第一次产生争用时结构:
第一个线程获取锁,第二个线程获取锁失败入队,此时才会初始化队列,结构空节点并将 head/tail 指向该空节点。
具体见 AbstractQueuedSynchronizer#enq。

2.4 条件队列

条件队列是期待条件成立的队列,是一个单向链表(nextWaiter),应用 firstWaiter/lastWaiter 指向队列的首尾节点。

java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject

/** First node of condition queue. */
private transient Node firstWaiter; // 条件队列的头节点
/** Last node of condition queue. */
private transient Node lastWaiter;  // 条件队列的尾节点

当线程获取锁胜利之后,执行 Conition.await(),开释锁并进入条件队列中期待,直到其余线程执行 Conition.signal 唤醒以后线程。

以后线程被唤醒后,从条件队列转移到同步队列,从新期待获取锁。

3. 独占模式

独占模式下,只有有一个线程占有锁,其余线程试图获取该锁将无奈取得成功。

3.1 获取锁 -acquire

独占模式下获取锁 / 资源,忽视中断,Lock#lock 的外部实现

java.util.concurrent.locks.AbstractQueuedSynchronizer#acquire

public final void acquire(int arg) {if (!tryAcquire(arg) &&                            
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) 
        selfInterrupt();}
  1. tryAcquire:尝试间接获取资源 / 锁,如果胜利则间接返回,失败进入下一步;
  2. addWaiter:获取资源 / 锁失败后,将以后线程退出同步队列的尾部,并标记为独占模式,返回新入队的节点;
  3. acquireQueued:使线程在同步队列期待获取资源,始终获取到后才返回,如果在期待过程中被中断过,则返回 true,否则返回 false。
  4. selfInterrupt:如果线程在期待过程中被中断过,在获取资源胜利之后,把中断状态补上。

3.1.1 tryAcquire

尝试获取资源,胜利返回 true。具体资源获取形式交由自定义同步器实现。

java.util.concurrent.locks.AbstractQueuedSynchronizer#tryAcquire

protected boolean tryAcquire(int arg) {throw new UnsupportedOperationException();
}

3.1.2 addWaiter

获取资源 / 锁失败后,将以后线程封装为新的节点,设置节点的模式(独占、共享),退出同步队列的尾部,返回该新节点。

java.util.concurrent.locks.AbstractQueuedSynchronizer#addWaiter

/**
 * Creates and enqueues node for current thread and given mode.
 *
 * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared // 独占模式、共享模式
 * @return the new node
 */
// 从队列尾部入队
private Node addWaiter(Node mode) {Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) { // 设置新的尾节点
            pred.next = node;
            return node;
        }
    }
    enq(node); // tail 为空,入队
    return node; // 返回以后的新节点
}

enq

从同步队列的尾部入队,如果队列不存在则进行初始化。

java.util.concurrent.locks.AbstractQueuedSynchronizer#enq

/**
 * Inserts node into queue, initializing if necessary. See picture above.
 * @param node the node to insert
 * @return node's predecessor
 */
private Node enq(final Node node) { // 从同步队列的尾部入队
    for (;;) {
        Node t = tail;
        if (t == null) { // Must initialize   // 队列为空,则创立一个空节点,作头节点
            if (compareAndSetHead(new Node()))
                tail = head;                  // 初始化实现后并没有返回,而是进行下一次循环
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) { // 队列不为空,则以后节点作为新的 tail // CAS 失败,可能会呈现尾分叉的景象,由下一次循环打消分叉
                t.next = node;                // 因为不是原子操作,入队操作先设置 prev 指针,再设置 next 指针,会导致并发状况下无奈通过 next 遍历到尾节点
                return t;                     // 返回以后节点的上一个节点(旧的尾节点)}
        }
    }
}

留神:

  1. 当第一次产生争用时,抢夺锁失败的线程入队,会先结构空节点(dummy node)作为 head/tail 节点进行初始化队列,再从队列尾部入队。
  2. 入队时,顺次设置 node.prev、tail、pred.next 指针,是非原子操作。
  3. 设置 prev 之后,若 CAS 设置 tail 失败,阐明其余线程先一步入队了,此时进入下一次循环会修改 prev 的指向。
  4. 因为入队是非原子操作,并发状况下可能无奈从 head 开始通过 next 遍历到尾节点 tail,然而从尾节点 tail 开始通过 prev 向前遍历能够拜访到残缺的队列。

3.1.3 acquireQueued

在同步队列自旋、期待获取资源直到胜利,返回期待期间的中断状态。

java.util.concurrent.locks.AbstractQueuedSynchronizer#acquireQueued

/**
 * Acquires in exclusive uninterruptible mode for thread already in
 * queue. Used by condition wait methods as well as acquire.
 *
 * @param node the node
 * @param arg the acquire argument
 * @return {@code true} if interrupted while waiting
 */
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) { // 上一个节点如果是头结点,阐明以后节点的线程能够尝试获取锁资源
                // 获取锁胜利,以后节点作为新的头节点,并且清理掉以后节点中的线程信息(也就是说头节点是个 dummy node)// 这里不会产生争用,不须要 CAS
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            // 上一个节点不是头节点,或者以后节点的线程获取锁失败,须要判断是否进入阻塞:// 1. 不能进入阻塞,则重试获取锁。2. 进入阻塞
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt()) // 阻塞以后线程,当从阻塞中被唤醒时,检测以后线程是否已中断,并革除中断状态。接着持续重试获取锁。interrupted = true;      // 标记以后线程已中断(如果线程在阻塞时被中断唤醒,会重试获取锁直到胜利之后,再响应中断)}
    } finally {if (failed)              // 自旋获取锁和阻塞过程中产生异样
            cancelAcquire(node); // 勾销获取锁
    }
}

在 acquireQueued 办法中,线程在自旋中次要进行两个判断:

  1. 是否获取锁
  2. 是否进入阻塞

具体代码流程:

  1. 在同步队列中自旋,若判断前继节点为头节点,则以后节点尝试获取锁。
  2. 若以后线程获取锁胜利,则将以后节点设为头节点,返回以后线程的中断状态。
  3. 若以后线程无奈获取锁、获取锁失败,则判断是否进入阻塞。
  4. 如果无奈进入阻塞,则持续自旋,否则进入阻塞。
  5. 线程从阻塞中被唤醒后,查看并标记线程的中断状态,从新进入自旋。

shouldParkAfterFailedAcquire

以后节点获取锁失败之后,通过校验上一个节点的期待状态,判断以后节点是否进入阻塞。
返回 true,可进入阻塞;返回 false,不可进入阻塞,需重试获取锁。

java.util.concurrent.locks.AbstractQueuedSynchronizer#shouldParkAfterFailedAcquire

/**
 * Checks and updates status for a node that failed to acquire. 
 * Returns true if thread should block. This is the main signal
 * control in all acquire loops.  Requires that pred == node.prev.
 *
 * @param pred node's predecessor holding status
 * @param node the node
 * @return {@code true} if thread should block
 */
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
        /*
         * This node has already set status asking a release // 以后节点曾经给它的上一个节点设置了唤醒信号
         * to signal it, so it can safely park.              // 以后节点能够进入阻塞
         */
        return true;
    if (ws > 0) {
        /*
         * Predecessor was cancelled. Skip over predecessors and // 上一个节点状态大于 0,阐明是已勾销状态 CANCELLED,不会告诉以后节点
         * indicate retry.                                       // 则始终往前找到一个期待状态的节点,并排在它的后边
         */                                                      // 以后节点不能进入阻塞,需重试获取锁
        do {node.prev = pred = pred.prev; // pred = pred.prev; node.prev = pred; // 跳过上一个节点,直到找到 waitStatus > 0 的节点} while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        /*
         * waitStatus must be 0 or PROPAGATE.  Indicate that we    // 上一个节点状态等于 0 或 PROPAGATE,阐明正在期待获取锁 / 资源
         * need a signal, but don't park yet.  Caller will need to // 此时须要给上一个节点设置唤醒信号 SIGNAL,但不间接阻塞
         * retry to make sure it cannot acquire before parking.    // 因为在阻塞前调用者须要重试来确认它的确不能获取资源
         */
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL); // 通过 CAS 将上一个节点的状态改为 SIGNAL
    }
    return false;
}

以后节点可能进入阻塞的条件是:具备其余线程来唤醒它。
通过设置上一个节点状态为 SIGNAL,以确保上一个节点在开释锁之后,可能唤醒以后节点。

分为三种状况:

  1. 上一个节点状态为 Node.SIGNAL,阐明以后节点已具备被唤醒的条件,能够进入阻塞。
  2. 上一个节点状态为已勾销,则把以后节点排到未勾销的节点前面,持续自旋不进入阻塞。
  3. 上一个节点状态为 0 或 PROPAGATE,阐明正在期待获取锁,则以后节点将上一个节点设为 SIGNAL,持续自旋不进入阻塞。

parkAndCheckInterrupt

进入阻塞,阻塞完结后,查看中断状态。

java.util.concurrent.locks.AbstractQueuedSynchronizer#parkAndCheckInterrupt

/**
 * Convenience method to park and then check if interrupted
 *
 * @return {@code true} if interrupted
 */
private final boolean parkAndCheckInterrupt() {LockSupport.park(this);
    return Thread.interrupted();}

cancelAcquire

线程在 acquireQueued 中自旋尝试获取锁的过程中,如果产生异样,会在 finally 代码块中执行 cancelAcquire,终止获取锁。

/**
 * Cancels an ongoing attempt to acquire.
 *
 * @param node the node
 */
private void cancelAcquire(Node node) { // 勾销获取锁
    // Ignore if node doesn't exist
    if (node == null)
        return;

    node.thread = null;

    // Skip cancelled predecessors // 跳过已勾销的前继节点,为以后节点找出一个无效的前继节点
    Node pred = node.prev;
    while (pred.waitStatus > 0)
        node.prev = pred = pred.prev;

    // predNext is the apparent node to unsplice. CASes below will
    // fail if not, in which case, we lost race vs another cancel
    // or signal, so no further action is necessary.
    Node predNext = pred.next;

    // Can use unconditional write instead of CAS here.        // 写操作具备可见性(volatile),因而这里无需应用 CAS
    // After this atomic step, other Nodes can skip past us.   // 把以后节点设为已勾销之后,其余节点寻找无效前继节点时会跳过以后节点
    // Before, we are free of interference from other threads.
    node.waitStatus = Node.CANCELLED;

    // If we are the tail, remove ourselves.
    if (node == tail && compareAndSetTail(node, pred)) { // 如果是尾节点,则出队
        compareAndSetNext(pred, predNext, null);
    } else {                                                      // 进入这里,阐明不是尾节点,或者是尾节点但出队失败,须要解决后继节点
        // If successor needs signal, try to set pred's next-link // 如果后继节点须要失去告诉,则尝试给它找一个新的前继节点
        // so it will get one. Otherwise wake it up to propagate. // 否则把后继节点唤醒
        int ws;
        if (pred != head &&                                       // 前继节点不是头节点
            ((ws = pred.waitStatus) == Node.SIGNAL ||             // 前继节点的状态为 SIGNAL 或者 前继节点的状态为未勾销且尝试设置为 SIGNAL 胜利
             (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
            pred.thread != null) {
            Node next = node.next;
            if (next != null && next.waitStatus <= 0)    // 后继节点存在且未勾销
                compareAndSetNext(pred, predNext, next); // 给后继节点设置一个新的前继节点(即后面找的无效节点),以后节点出队
        } else {unparkSuccessor(node); // 如果存在后继节点,这里阐明无奈给后继节点找到新的前继节点(可能前继节点是 head,或者前继节点生效了),间接唤醒该后继节点
        }

        node.next = node; // help GC
    }
}

节点 node 勾销获取锁,阐明以后节点 node 状态变为已勾销,成为一个有效节点。

须要思考如何解决节点 node 的后继节点:

  1. 无后继节点,则须要将最初一个无效节点(waitStatus <= 0)设为 tail。
  2. 存在后继节点,则须要将它挂在最初的无效节点之后,后续由该节点来唤醒后继节点。
  3. 存在后继节点,且找不到无效的前继节点,则间接把该后继节点唤醒。

unparkSuccessor

唤醒以后节点的后继节点。

java.util.concurrent.locks.AbstractQueuedSynchronizer#unparkSuccessor

/**
 * Wakes up node's successor, if one exists.
 *
 * @param node the node
 */
// 唤醒以后节点的后继节点 
private void unparkSuccessor(Node node) {
    /*
     * If status is negative (i.e., possibly needing signal) try
     * to clear in anticipation of signalling.  It is OK if this
     * fails or if status is changed by waiting thread.
     */
    int ws = node.waitStatus; // 如果以后节点的状态为已勾销,则不变;如果小于 0(有可能后继节点须要以后节点来唤醒),则清零。if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0); // CAS 失败也无所谓(阐明后继节点的线程先一步批改了以后节点的状态),因为接下来会手动唤醒后继节点

    Node s = node.next;
    if (s == null || s.waitStatus > 0) { // 后继节点为空,或已勾销,则从 tail 开始向前遍历无效节点
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t; // // 留神! 这里找到了之后并没有 return, 而是持续向前找
    }
    if (s != null)
        LockSupport.unpark(s.thread); // 唤醒后继节点(或者是队列中距离 head 节点最近的无效节点)的线程
}

通常状况下, 要唤醒的节点就是本人的后继节点。如果后继节点存在且也在期待锁, 那就间接唤醒它。
然而有可能存在 后继节点勾销期待锁 的状况,此时从尾节点开始向前找起, 直到找到间隔 head 节点最近的未勾销的节点,对它进行唤醒。

为什么不从以后节点向后遍历无效节点呢?

  1. 以后节点可能是尾节点,不存在后继节点。
  2. 入队时先设置 prev 指针,再设置 next 指针(见 AbstractQueuedSynchronizer#enq),是非原子操作,依据 prev 指针往前遍历比拟精确。

3.2 获取锁 -acquireInterruptibly

比照 acquire,两者对获取锁过程中产生中断的解决不同。

  1. acquire 期待锁的过程产生中断,会等到获取锁胜利之后,再解决中断。
  2. acquireInterruptibly 期待锁的过程产生中断,会立刻抛出 InterruptedException,不再期待获取锁。

java.util.concurrent.locks.AbstractQueuedSynchronizer#acquireInterruptibly

public final void acquireInterruptibly(int arg)
        throws InterruptedException {if (Thread.interrupted())
        throw new InterruptedException();
    if (!tryAcquire(arg))
        doAcquireInterruptibly(arg);
}

java.util.concurrent.locks.AbstractQueuedSynchronizer#doAcquireInterruptibly

/**
 * Acquires in exclusive interruptible mode.
 * @param arg the acquire argument
 */
private void doAcquireInterruptibly(int arg)
    throws InterruptedException {final Node node = addWaiter(Node.EXCLUSIVE);
    boolean failed = true;
    try {for (;;) {final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {setHead(node);
                p.next = null; // help GC
                failed = false;
                return;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException(); // 线程在阻塞期待锁的过程中,被中断唤醒,则放弃期待锁,间接抛出异样}
    } finally {if (failed)
            cancelAcquire(node);
    }
}

3.3 开释锁 -release

独占模式下开释锁 / 资源,是 Lock#unlock 的外部实现。

java.util.concurrent.locks.AbstractQueuedSynchronizer#release

public final boolean release(int arg) {if (tryRelease(arg)) { // 开释锁资源
        Node h = head;
        if (h != null && h.waitStatus != 0) // head.waitStatus == 0,阐明 head 节点后没有须要唤醒的节点
            unparkSuccessor(h); // 唤醒 head 的后继节点
        return true;
    }
    return false;
}
  1. tryRelease:开释锁资源,开释胜利则进入下一步。
  2. unparkSuccessor:如果同步队列的头节点存在且满足 waitStatus != 0,则唤醒后继节点。

java.util.concurrent.locks.AbstractQueuedSynchronizer#tryRelease

protected boolean tryRelease(int arg) {throw new UnsupportedOperationException();
}

头节点 h 的状态:

  • h.waitStatus == 0,同步队列中的节点初始状态为 0,阐明没有须要唤醒的后继节点。
  • h.waitStatus < 0,独占模式下,阐明是 SIGNAL 状态,此时具备后继节点期待唤醒,见 AbstractQueuedSynchronizer#shouldParkAfterFailedAcquire。
  • h.waitStatus > 0,阐明是 CANCELLED 状态,头节点是由勾销获取锁的节点而来的,保险起见,查看有没有须要唤醒的后继节点。

相干浏览:
浏览 JDK 8 源码:AQS 中的独占模式
浏览 JDK 8 源码:AQS 中的共享模式
浏览 JDK 8 源码:AQS 对 Condition 的实现

作者:Sumkor

正文完
 0