AQS(一)独占锁(基于JDK 8)

@

1 介绍

在开始之前,先推荐一本书和两个博客,书是《Java并发实现原理:JDK源码剖析_出版社 电子工业出版社; 第1版》,可从我同名公众号下载。博客是 https://segmentfault.com/a/1190000016058789https://blog.csdn.net/anlian523/article/details/106598910。

AQS(AbstractQueuedSynchronizer) 是 Java 很多并发框架的基础,主要数据结构是两个队列,双向同步队列CLH 和一个单向条件队列。只有同步队列中的节点才能获取锁。同步队列的唤醒指的是解除堵塞 unpark,条件队列中所谓的唤醒是把节点从条件队列移到同步队列,让节点有机会去获取锁。

利用AQS 的类有如下几个,独占锁 ReentrantLock,共享锁 Semaphore 和 CountDownLatch,写独占,读共享锁 ReentrantReadWriteLock,另外 ThreadPoolExecutor 中的 Worker 也实现了AQS。

对于几个锁来说,实现方式都是一样的,有一个内部类 Sync,继承了 AQS,锁内有成员变量 sync。如下图所示,几个锁就是下面的 LockClass。

1.1 state

state 在各个锁含义不同。

在 ReentrantLock 中,state 为 0表示当前没有锁,大于0表示当前已经有线程拥有锁,且重入了 state 次。在获取锁时,state 增加;释放锁时,state 减少。

在 Semaphore 中,state 表示许可证数目,获取锁减少,释放锁增加。

在 CountDownLatch 中,state 表示倒计时数目,释放会减少。

1.2 Node

Node 是 AQS 的内部类,有着五种状态 waitStatus,用于 sync queue 中节点的前后指针 prev 和 next,内部线程 thread 和 nextWaiter。

Node用来构建同步队列节点,nextWaiter标识同步锁是独占锁还是共享锁;Node用来构建条件队列节点,nextWaiter指向单向链表下一个节点。

最重要的是五种状态:CANCELLED、SIGNAL、CONDITION、PROPAGATE、0。

  • CANCELLED(1):表示当前结点已取消调度。当timeout或被中断(响应中断的情况下),会触发变更为此状态,进入该状态后的结点将不会再变化。
  • SIGNAL(-1):表示后继结点在等待当前结点唤醒。后继结点入队时,会将前继结点的状态更新为SIGNAL。
  • CONDITION(-2):表示结点等待在Condition上,当其他线程调用了Condition的signal()方法后,CONDITION状态的结点将从等待队列转移到同步队列中,等待获取同步锁。
  • PROPAGATE(-3):共享模式下,前继结点不仅会唤醒其后继结点,同时也可能会唤醒后继的后继结点
  • 0:新结点入队时的默认状态。

此外要注意 WaitStatus > 0 暗示了就是 CANCELLED。

 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;

   			// 下面是5种状态
        /** 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;
   			//省略了一种情况,即初始化时,为0			
  			
        volatile int waitStatus;
   			// sync queue 的两个指针
        volatile Node prev;
        volatile Node next;
        volatile Thread thread;
   			// 不同队列中的节点,含义不同
   			// 这里表示是独占锁还是共享锁
        Node nextWaiter;

在 AQS 中,需要记录双向队列的头 head 和尾 tail。

// 头结点,thread成员固定为null,要处理的是下一个节点
private transient volatile Node head;
// 尾节点,请求锁失败的线程,总是放到队尾
private transient volatile Node tail;

1.3 模板方法

下列方法在 AQS 中会直接抛出异常,如果需要自己的类实现 AQS,只需要重写下面的方法即可。

方法名 描述
protected boolean isHeldExclusively() 该线程是否正在独占资源。只有用到Condition才需要去实现它。
protected boolean tryAcquire(int arg) 独占方式。arg为获取锁的次数,尝试获取资源,成功则返回True,失败则返回False。
protected boolean tryRelease(int arg) 独占方式。arg为释放锁的次数,尝试释放资源,成功则返回True,失败则返回False。
protected int tryAcquireShared(int arg) 共享方式。arg为获取锁的次数,尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
protected boolean tryReleaseShared(int arg) 共享方式。arg为释放锁的次数,尝试释放资源,如果释放后允许唤醒后续等待结点返回True,否则返回False。

1.4 其他

// 对于独占锁来说,如果有线程持有锁,则记录该线程
// 可以理解为是现在 head 节点的线程,拿出来清空以后放在这里
// 这就是获取锁,且正在执行的线程
private transient Thread exclusiveOwnerThread;
// CAS相关,使用时看名字就行
protected final boolean compareAndSetState(int expect, int update) {
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
private final boolean compareAndSetHead(Node update) {
    return unsafe.compareAndSwapObject(this, headOffset, null, update);
}
private final boolean compareAndSetTail(Node expect, Node update) {
    return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}
private static final boolean compareAndSetWaitStatus(Node node, int expect,int update) {
    return unsafe.compareAndSwapInt(node, waitStatusOffset, expect, update);
}
private static final boolean compareAndSetNext(Node node, Node expect, Node update) {
    return unsafe.compareAndSwapObject(node, nextOffset, expect, update);
}

2 锁的获取 acquire

在 AQS 的分析中,需要先把流程理顺,然后再具体分析。不需要考虑其中的 1.3 的模板方法是怎么实现的,假定已经实现了功能即可。

对于下面的方法,总是先简单尝试一下,如果不行,会进行更复杂的自旋保证成功。

详细来说 :首先尝试获取(tryAcquire),失败后将当前线程包装到一个 Node 中,插入同步队列尾部(addWaiter),并在队列中不断自旋尝试(acquireQueued),满足堵塞条件(shouldParkAfterFailedAcquire)则会堵塞(parkAndCheckInterrupt)以减轻自旋的 CPU 消耗,如果堵塞后被唤醒会继续自旋尝试,直到成功后返回,并根据中断情况设置中断(selfInterrupt);或者因为异常抛出没有成功,则取消该线程所在的节点(cancelAcquire)。

2.1 acquire

首先尝试一次 tryAcquire,成功则说明锁已经获取,结束;失败则需要继续处理,做法是使用 addWaiter 将当前线程插入同步队列尾部,然后使用 acquireQueued 在队列中完成锁的获取。acquireQueued 返回值表示在执行期间是否被中断,执行期间不响应中断,只是把中断状态记下来。返回 true 表示被中断过,使用 selfInterrupt 来中断即可。

/**
     * Acquires in exclusive mode, ignoring interrupts.  Implemented
     * by invoking at least once {@link #tryAcquire},
     * returning on success.  Otherwise the thread is queued, possibly
     * repeatedly blocking and unblocking, invoking {@link
     * #tryAcquire} until success.  This method can be used
     * to implement method {@link Lock#lock}.
     *
     * @param arg the acquire argument.  This value is conveyed to
     *        {@link #tryAcquire} but is otherwise uninterpreted and
     *        can represent anything you like.
     */
public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
// 中断自己
static void selfInterrupt() {
        Thread.currentThread().interrupt();
    }

2.2 addWaiter

新建一个节点 node,先尝试直接入队,失败的话使用 enq 入队。

 /**
     * 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;
      	// 将 node 放入尾部,有三步
      	// 下面先修改前指针prev,设置尾部成功后,再建立后指针pred
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
      	// 前面无法成功,需要enq入队
        enq(node);
        return node;
    }

// 这里说明在CLH队列中,nextWaiter就是表示独占或者共享
Node(Thread thread, Node mode) {     // Used by addWaiter
            this.nextWaiter = mode;
            this.thread = thread;
        }

2.3 enq

enq 保证入队成功,如果 t 为 null,说明是初始化,新建一个空节点 new Node 放在头部,并设置 tail = head;

否则,试图将 node 放入尾部,有三步,修改 node 和原尾部 t 的指针,CAS 修改尾部。

和 addWaiter 一样,先修改 node 的前指针,然后 CAS,然后修改 t 的后指针。

/**
     * 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)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

2.4 acquireQueued

最重要的方法

使得在队列中的 node 获取独占锁,使用 for 自旋和堵塞/唤醒来实现,在不抛出异常的情况下一定会成功,返回堵塞期间是否被中断。

在 for 循环中,先尝试获取 node 前一个节点 p,如果 p 是 head,执行 tryAcquire,成功的话,做一些操作并返回;失败的话,会检查 shouldParkAfterFailedAcquire,如果返回 true,表示可以堵塞,执行 parkAndCheckInterrupt,堵塞线程,直到其他线程唤醒该 node,唤醒后继续执行 for 循环。

如果存在抛出异常的情况,finally 中的 if 为 true,则取消 node。

final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
              	// 获取之前的节点
                final Node p = node.predecessor();
              	// 如果前一个是head,尝试一次 tryAcquire
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                  	// 返回中断情况
                    return interrupted;
                }
              	// should... 返回能否堵塞,如果能,执行park...
              	// park... 的返回堵塞期间是否被中断
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
          	// 异常退出,失败,取消获取
            if (failed)
                cancelAcquire(node);
        }
    }

// 不修改节点的状态 waitStatus
    private void setHead(Node node) {
        head = node;
        node.thread = null;
        node.prev = null;
    }

// Node的方法
final Node predecessor() throws NullPointerException {
            Node p = prev;
            if (p == null)
                throw new NullPointerException();
            else
                return p;
        }

2.5 shouldParkAfterFailedAcquire

should Park After Failed Acquire,意思是在失败后应该堵塞吗?返回 true表示应该堵塞,接下来执行 parkAndCheckInterrupt。

  • node 前一个节点是 pred,如果 ws 是 SIGNAL,根据 1.2 中 SIGNAL 的含义,后续的 node 等待 pred 唤醒,这些已经完成了,所以返回 true。

  • ws >0 表示是 CANCELLED,会不断地往前找,找到第一个未取消的节点(pred.waitStatus<=0),设置为新的 pred。

  • 否则,尝试将 ws 修改为 SIGNAL。后两种情况返回 false。

在多线程中,由于节点不断地修改,可能会多次返回 false。下面假定其他线程的修改对两个节点没有影响,分析一下需要多少次返回 true:

  • 在第一个 if 中,直接返回 true;

  • 在第二个 if 中,会先找到一个未取消的新节点作为 pred,下一次进入该方法有两种情况,一个是第一个 if ,返回 true,另一个是 else 的情况;

  • 在 else中,本次会尝试修改ws,如果修改成功,则下一次返回 true,否则下次再次进入 else 修改。

/**
     * 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
     */ 
		// 如果 node 前一个节点状态已经是 SIGNAL,可以堵塞了,返回 true
		// 其余情况是尽量往 SIGANL 靠,返回 false,在多次进入方法后有望变成 true
    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;
      	// 等价于 else if (ws > 0)
        if (ws > 0) {
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /*
             * waitStatus must be 0 or PROPAGATE.  Indicate that we
             * need a signal, but don't park yet.  Caller will need to
             * retry to make sure it cannot acquire before parking.
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

/**
     * Convenience method to park and then check if interrupted
     *
     * @return {@code true} if interrupted
     */
    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }
// LockSupport.java
public static void park(Object blocker) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
  			// park后不会释放,等待unpark
        UNSAFE.park(false, 0L);
        setBlocker(t, null);
    }

2.6 cancelAcquire

该方法是在 acquireQueued 失败后调用的,目的是从队列中删除队列中的 node,或者直接唤醒 node 后续节点。

1 比较复杂的地方是处理 node 非 tail 时的操作: if (pred != head &&((ws = pred.waitStatus) == Node.SIGNAL ||(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) && pred.thread != null),可以分解为三部分:

​ ① pred != head

​ ② ((ws = pred.waitStatus) == Node.SIGNAL ||(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL)))

​ ③ pred.thread != null

也就是 (pred 不是 head) &&(ws 是 SIGNAL 或者 ws 不是 CANCELLED,然后尝试修改为 SIGNAL 成功) && (pred 的线程不是 null)

2 最后一句node.next = node;看不懂。

/**
     * Cancels an ongoing attempt to acquire.
     *
     * @param node the node
     */ 
// 在获取失败后删除队列中的 node,或者直接唤醒 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 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.
      	// 用于 CAS 比较的 Node
        Node predNext = pred.next;

        // Can use unconditional write instead of CAS here.
        // After this atomic step, other Nodes can skip past us.
        // Before, we are free of interference from other threads.
      	// 将 Node 设置为 CANCELLED
        node.waitStatus = Node.CANCELLED;

        // If we are the tail, remove ourselves.
      	// node 在尾部,只需要将 pred移动到尾部,pred.next 修改为 null即可
        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 ||
                 (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
                pred.thread != null) {
                Node next = node.next;
              	// 尝试将 pred.next 修改为 next,也就是删除了node
                if (next != null && next.waitStatus <= 0)
                    compareAndSetNext(pred, predNext, next);
            } else {
              	// 直接唤醒 node 下一个节点
              	// 方法分析见第三节
                unparkSuccessor(node);
            }
						// 这一步看不懂??
            node.next = node; // help GC
        }
    }

3 锁的释放release

流程如下:尝试释放(tryRelease),成功后解除 head 后一个节点(unparkSuccessor),并返回 true;失败则返回 false 。

注意到 release 只执行一次,而 acquire 内的 acquireQueued 会自旋和堵塞,直到成功。这是因为如果当前线程持有锁,释放是没有竞争的。

/**
     * Releases in exclusive mode.  Implemented by unblocking one or
     * more threads if {@link #tryRelease} returns true.
     * This method can be used to implement method {@link Lock#unlock}.
     *
     * @param arg the release argument.  This value is conveyed to
     *        {@link #tryRelease} but is otherwise uninterpreted and
     *        can represent anything you like.
     * @return the value returned from {@link #tryRelease}
     */
    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

3.1 unparkSuccessor

唤醒 node 的下一个非 CANCELLED 节点,unpark 节点的线程。

有一个难点是如果 s 不满足条件,考虑从尾向前查找,这是因为前面的尾分叉操作,原因在 3.2 详述。

    /**
     * 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.
         */
      	// 这里的目的是将node的状态恢复为初始0,当然失败也无所谓
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        /*
         * Thread to unpark is held in successor, which is normally
         * just the next node.  But if cancelled or apparently null,
         * traverse backwards from tail to find the actual
         * non-cancelled successor.
         */
      	// 查找 node 下一个非空非CANCELLED 节点
      	// 如果 next 不满足条件,则尝试从尾向前找
      	// 原因见 3.2
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
      	// 解除堵塞,线程会从 acquireQueued 的 parkAndCheckInterrupt
      	// 继续运行
        if (s != null)
            LockSupport.unpark(s.thread);
    }

3.2 从尾遍历的原因(尾分叉)

在 addWaiter 和 enq 中,会发现向尾部插入的时候,总是先修改前指针 prev,CAS 设置尾部成功后,再修改后指针 next,正是因为两个节点先修改的前指针,所以如果前指针 prev 正确,后指针 next 不一定正确甚至不存在,只能从 tail 不断利用前指针往前查找。

 /**
     * 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;
      	// 将 node 放入尾部,有三步
      	// 下面先修改node前指针prev,设置尾部成功后,再建立原尾部后指针next
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
      	// 前面无法成功,需要enq入队
        enq(node);
        return node;
    }
    
/**
     * 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 {
              	// 和 addWaiter 中一模一样
                // 下面先修改node前指针prev,设置尾部成功后,再建立原尾部后指针next
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

下面以两个线程为例,如果有两个线程执行了 node.prev = t,接下来线程1 CAS 成功,会发生什么。

4 其他方法

上面只介绍了最普通的获取和释放方法,没有超时,对于中断会记下来而不在中间响应。这两个方法被 ReentrantLock 的 lock/unlock 使用。

另外还有一些其他的方法,能够中断或者超时返回。具体使用的时候看名字就行。下面举几个例子,简单看一下。

//在处理完成之后才会根据是否有中断决定是否执行 selfInterrupt
public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
}
//如果有中断,会直接抛出异常
public final void acquireInterruptibly(int arg)
            throws InterruptedException {
        // 先检查是否中断
        if (Thread.interrupted())
            throw new InterruptedException();
        // 在下面的方法中如果有中断也直接抛出异常
        if (!tryAcquire(arg))
            doAcquireInterruptibly(arg);
}
//////-------------------------------------------------
// 响应中断,在中断后直接抛出异常
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);
        }
}
// 除了对中断抛出异常外,还会根据时间,超过时间返回 false
static final long spinForTimeoutThreshold = 1000L;
private boolean doAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (nanosTimeout <= 0L)
            return false;
        final long deadline = System.nanoTime() + nanosTimeout;
        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 true;
                }
                nanosTimeout = deadline - System.nanoTime();
              	// 此时会失败,在finally 处取消
                if (nanosTimeout <= 0L)
                    return false;
              	// 这里是说,只有纳秒数nanosTimeout超过1000,才有堵塞的必要,
                if (shouldParkAfterFailedAcquire(p, node) &&
                    nanosTimeout > spinForTimeoutThreshold)
                  	// 堵塞 nanosTimeout 这么长时间
                    LockSupport.parkNanos(this, nanosTimeout);
                if (Thread.interrupted())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
}
// 返回中断情况 boolean ,由上层自行处理
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)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
 }
posted @ 2021-03-28 16:22  Java与大数据进阶  阅读(156)  评论(0编辑  收藏  举报