java AQS源码阅读

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/zhuge134/article/details/89442559

####addWaiter
该方法用于向等待队列中添加一个节点,对于获取独占锁的方法(acquire)传入的mode是EXCLUSIVE,获取共享锁的传入的是SHARED

private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    //先尝试快速添加到队列中,如果失败则进入enq方法
    Node pred = tail;
    if (pred != null) {
        //这一句值得仔细琢磨,先将node的前驱节点设为tail
        node.prev = pred;
        //使用cas方法保证多线程情况下的同步
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}

这里使用cas保证多线程竞争情况下的同步。有一个值得注意的地方是先将新节点的前驱节点设为旧的tail,然后再将新节点设为tail节点,如果不这么做在执行完compareAndSetTail的一瞬间,新的tail节点与前面的节点实际上是断裂的,而这时候有可能其他线程会使用tail节点往前遍历(unparkSuccessor)
####enq

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;
            }
        }
    }
}

循环,直到节点插入队列尾部,如果尾节点是null,那么初始化队列

####acquireQueued
已经添加到等待队列中的节点获取独占锁,不响应线程中断,acquire方法和条件等待方法会使用该方法

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);
    }
}

思考这里为什么以前驱节点是头结点作为判断条件???

####shouldParkAfterFailedAcquire
检查并更新获取锁失败的节点的状态,判断等待在该节点上的线程是否可以阻塞。这个方法是在获取锁的循环中最主要的信号控制的地方。

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.
         * 如果前驱节点的状态已经是SIGNAL,那么本节点可以安心地阻塞了,反正前驱节点释放锁时一定会通知的
         * 如果前驱节点正常释放锁,会通过unparkSuccessor通知到后继节点,
         * 如果前驱节点取消了,在cancelAcquire方法中一定会保证最终能够通知到后继节点,cancelAcquire方法考虑到了各种情形
         */
        return true;
    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.
         * 剩余的情况是0或者PROPAGATE状态,意味着我们需要得到通知,但是暂时不挂起
         * 将前驱节点的状态设置为SIGNAL,然后我们再尝试获取一次锁,如果还获取不到那么再挂起
         * 思考一下这里如果设置状态失败了会怎样???什么情况下会设置失败???
         */
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    //除了前驱节点的状态已经是SIGNAL的情况,其他情况均不挂起
    return false;
}

思考一下compareAndSetWaitStatus(pred, ws, Node.SIGNAL);这一句,如果设置状态失败了会怎样???什么情况下会设置失败???

  1. 什么情况下会设置失败,也就是说在此期间前驱节点状态变化了。可能是前驱节点取消等待
  2. 如果设置状态失败了会怎样?如果设置失败,首先会接着去获取锁,如果仍然获取不到锁,那么又会再次进入这个方法,并设置状态

####parkAndCheckInterrupt
将线程挂起,并检测挂起期间是否被中断过

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

我们看一下挂起的线程在哪些情况下会被唤醒:

/**
 * Disables the current thread for thread scheduling purposes unless the
 * permit is available.
 *
 * <p>If the permit is available then it is consumed and the call returns
 * immediately; otherwise
 * the current thread becomes disabled for thread scheduling
 * purposes and lies dormant until one of three things happens:
 * 当前线程会陷入阻塞直到一下三种情况中的任意一种发生:
 *
 * <ul>
 * <li>Some other thread invokes {@link #unpark unpark} with the
 * current thread as the target; or
 * 其他的线程对该线程调用unpark方法
 *
 * <li>Some other thread {@linkplain Thread#interrupt interrupts}
 * the current thread; or
 * 其他的线程对该线程调用interrupt方法
 *
 * <li>The call spuriously (that is, for no reason) returns.
 * </ul>
 *
 * <p>This method does <em>not</em> report which of these caused the
 * method to return. Callers should re-check the conditions which caused
 * the thread to park in the first place. Callers may also determine,
 * for example, the interrupt status of the thread upon return.
 *
 * @param blocker the synchronization object responsible for this
 *        thread parking
 * @since 1.6
 */
public static void park(Object blocker) {
    Thread t = Thread.currentThread();
    setBlocker(t, blocker);
    UNSAFE.park(false, 0L);
    setBlocker(t, null);
}

后面会看到,如果前面的线程释放锁或者取消等待,那么会通过unpark方法将后继线程唤醒。

####cancelAcquire
最后,如果循环获取锁的过程中发生了异常,就会走到这一步。
思考会发生什么异常???什么情况下会发生异常???

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.
    //将前驱节点的next应用保存下来,后面cas会用到
    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.waitStatus = Node.CANCELLED;

    // If we are the tail, remove ourselves.
    //如果该节点是tail节点,那么移除该节点就行
    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.
        // 如果后继节点需要通知,那么设置前驱节点的状态为SIGNAL,
        // 否则唤醒后继节点
        int ws;
        // 条件:前驱节点不是头结点(如果前驱节点是头结点,那么就需要唤醒后继节点);
        // 前驱节点的状态是SIGNAL或者能够设置为SIGNAL
        // 前驱节点的线程不是空,也即有活跃的线程保证能够通知到后继节点
        if (pred != head &&
            ((ws = pred.waitStatus) == Node.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);
        }

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

####release
释放独占锁。通常用于实现Lock.unlock方法

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        //思考:什么情况下head是null??? 
        // 为什么 waitStatus==0时就不需要唤醒后继节点???
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

思考一个问题:假设A线程正在释放锁,运行到了unparkSuccessor方法的646行之前(还没有将状态设置为0),
这时后继节点的线程B正好运行到了shouldParkAfterFailedAcquire方法的802行(之前已经有一轮没获取到锁,前驱节点的状态已经被设置为SIGNAL),
这时线程A继续运行,将unparkSuccessor方法跑完,
接着线程B接着运行,接下来线程B将会阻塞,但是前面线程A已经完成了锁的释放,已经unpark线程B过了,不会再次unpark,那么这种情况下线程B岂不是再也得不到唤醒??

这个问题的答案就在LockSupport.unpark方法中,我们看一下unpark方法的注释

/**
 * Makes available the permit for the given thread, if it
 * was not already available.  If the thread was blocked on
 * {@code park} then it will unblock.  Otherwise, its next call
 * to {@code park} is guaranteed not to block. This operation
 * is not guaranteed to have any effect at all if the given
 * thread has not been started.
 * 使得线程得到许可运行。
 * 如果线程已经被阻塞,那么该方法让线程接触阻塞状态;
 * 如果线程没有被阻塞,那么该方法是的下一次对该线程调用park方法不会阻塞,会立即返回
 *
 * @param thread the thread to unpark, or {@code null}, in which case
 *        this operation has no effect
 */
public static void unpark(Thread thread) {
    if (thread != null)
        UNSAFE.unpark(thread);
}

看到这里是不是瞬间明白了,线程A在死之前已经将后事交代好了,留下了锦囊妙计,所以无论B线程的park在A线程的unpark之前还是之后,B线程都肯定不会阻塞。

####获取共享锁acquireShared
不响应中断

public final void acquireShared(int arg) {
    //没有获取到资源量
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}

#####doAcquireShared

private void doAcquireShared(int arg) {
    //addWaiter方法我们前面已经讲过,就是添加一个等待节点到队列尾部,
    //因为是获取共享锁,所以这里传入的是SHARED
    final Node node = addWaiter(Node.SHARED);
    //标志获取锁失败
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    //如果成功获取到锁,那么向后传播,通知后面等待的节点
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    if (interrupted)
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            //阻塞,等待唤醒,循环直到获取锁
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

doAcquireShared方法和acquireQueued方法总的逻辑差不多,都是添加一个节点到队列尾部,然后线程在这个节点上循环直到获取锁,不同是doAcquireShared方法在获取到资源之后会通知后继节点继续获取资源,所以我们看一下setHeadAndPropagate方法

private void setHeadAndPropagate(Node node, int propagate) {
    Node h = head; // Record old head for check below
    //首先设置头结点
    setHead(node);
    /*
     * Try to signal next queued node if:
     *   Propagation was indicated by caller,
     *     or was recorded (as h.waitStatus either before
     *     or after setHead) by a previous operation
     *     (note: this uses sign-check of waitStatus because
     *      PROPAGATE status may transition to SIGNAL.)
     * 决定是否往后传播的条件:
     * propagate即剩余资源量大于零,或者状态<0
     * and
     *   The next node is waiting in shared mode,
     *     or we don't know, because it appears null
     *
     * The conservatism in both of these checks may cause
     * unnecessary wake-ups, but only when there are multiple
     * racing acquires/releases, so most need signals now or soon
     * anyway.
     */
     // 这段代码没看懂,不知道什么情况下h==null,或者(h = head) == null
     // 
    if (propagate > 0 || h == null || h.waitStatus < 0 ||
        (h = head) == null || h.waitStatus < 0) {
        Node s = node.next;
        // 这里之所以s可能是null可以看addWaiter方法,cas tail节点的一瞬间,前驱节点是没有指向tail节点的引用的
        if (s == null || s.isShared())
            doReleaseShared();
    }
}

#####doReleaseShared

private void doReleaseShared() {
    /*
     * Ensure that a release propagates, even if there are other
     * in-progress acquires/releases.  This proceeds in the usual
     * way of trying to unparkSuccessor of head if it needs
     * signal. But if it does not, status is set to PROPAGATE to
     * ensure that upon release, propagation continues.
     * Additionally, we must loop in case a new node is added
     * while we are doing this. Also, unlike other uses of
     * unparkSuccessor, we need to know if CAS to reset status
     * fails, if so rechecking.
     * 确保释放锁之后能够传递下去,即是有其他线程正在获取锁或者释放锁。
     * 如果头结点的状态是signal,那么唤醒后继节点。如果头结点状态不是signal,那么将状态设为PROPAGATE,以确保一旦线程释放锁,能够正常传播下去
     */
    for (;;) {
        Node h = head;
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            if (ws == Node.SIGNAL) {
                //由于当前线程释放了锁,所以必须要将状态设为0,防止后继节点阻塞(见shouldParkAfterFailedAcquire)
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // loop to recheck cases
                unparkSuccessor(h);
            }
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        if (h == head)                   // loop if head changed
            break;
    }
}

虽然懂大概的意思,但是深究细节的话还是有很多不明白的地方。暂时先跳过,继续看释放锁的代码

####releaseShared
释放锁的核心代码也是doReleaseShared方法,这个方法还得慢慢啃啊。

展开阅读全文

没有更多推荐了,返回首页