细品 AbstractQueuedSynchronizer 底层源码 (二)

AQS主要模板方法剖析

前言

上文主要介绍了acquire的方法,本文将继续探讨剩下的一些方法。

正文

关于acquireInterruptibly 这个方法和acquire代码差异不大,主要在896行多了个中断异常处理,acquire是直接返回中断的状态值

  if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }

关于tryAcquireNanos方法也是大同小异,差异在doAcquireNanos内关于获取锁失败后判断是否阻塞的时候加个超时的判断条件,代码在932行

 if (shouldParkAfterFailedAcquire(p, node) &&
 // 加个自旋的超时判断
                    nanosTimeout > spinForTimeoutThreshold)
                    LockSupport.parkNanos(this, nanosTimeout);

看下release 释放锁方法

public final boolean release(int arg) {
	// 由子类覆写
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
            	// 唤醒下一个节点,通过调用LockSupport来实现
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

接下来看下获取共享锁的方法acquireShared

public final void acquireShared(int arg) {
		// 子类覆写
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }
     private void doAcquireShared(int arg) {
     	// 添加共享模式的节点到等待队列中
        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);
                    // r>=0 说明共享锁获取成功
                    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);
        }
    }

如果读者看过acquire方法或者看过笔者上篇关于AQS的介绍文章的话,发现两种方法差异不大,主要在setHeadAndPropagate,独占锁是没有这方法的

  /**
     * Sets head of queue, and checks if successor may be waiting
     * in shared mode, if so propagating if either propagate > 0 or
     * PROPAGATE status was set.
     *
     * @param node the node
     * @param propagate the return value from a tryAcquireShared
     */
     // propagat值为之前tryAcquireShared返回的值,如果大于0 ,表示会继续扩散
     // 后续节点扔可以获得锁,会继续唤醒后续节点
    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.)
         * 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.
         */
         // propagate大于0或者节点状态为负包括以及头结点的检查
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            // 后续节点为null或者是共享,继续唤醒节点
            if (s == null || s.isShared())
                doReleaseShared();
        }
    }
     /**
     * Release action for shared mode -- signals successor and ensures
     * propagation. (Note: For exclusive mode, release just amounts
     * to calling unparkSuccessor of head if it needs signal.)
     */
    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.
         */
        for (;;) {
        	// 头节点持有锁
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                // SIGNAL状态
                if (ws == Node.SIGNAL) {
                	// cas赋值0成功后,就唤醒节点
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                        // 唤醒节点
                    unparkSuccessor(h);
                }
                // ws=0,尝试cas赋值PROPAGATE
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            // 终止自旋,因为共享锁说明也有其他线程正在使用,会修改head的值
            // 即自旋到确定没有其他线程修改的情况下。
            if (h == head)                   // loop if head changed
                break;
        }
    }

关于doReleaseShared 这个方法,看似代码不多,其实思想还是很复杂的。
首先doReleaseShared 这个方法在acquireShared中调用了一次,释放锁的方法releaseShared 也会调用doReleaseShared ,这就耐人寻味了。
setHeadAndPropagate如果头结点下的结点为null或者是共享结点,就唤醒下个节点,因为是共享锁,所以支持多线程持有锁。
而在doReleaseShared中了解只有头节点才获取锁,代码里只判断头节点的等待状态,通过cas更改status值为0来唤醒节点。
因为是共享锁,支持多线程喽,所以就会有多个线程调用acquireSharedreleaseShared 方法了。在看一下终止自旋条件if (h == head) 说明只有头节点不被易主的时候才能结束。
说的难以理解,举例解释一下。
比如现有的同步队列,有abcd四个节点
head->a->b->c->d
a成为head后
head(a)->b->c->d
a将会调用doReleaseShared唤醒b,方便描述用doReleaseShared(a)来表达a线程的调用。唤醒后b将成为新的head
head(b)->c->d
这样下去b也会调用doReleaseShared方法去唤醒c,然后不停的循环下去,不断增加唤醒的线程操作,大大的提升了队列唤醒线程的效率,而代码中的第一个if循环以及cas操作确保诸多线程中只有一个能唤醒成功。不知有人是否好奇此时唤醒b线程的线程a在干嘛,线程a在唤醒b后继续往下走
,走到if (h == head)发现头结点变更了,就进行自旋。
如果doReleaseShared(a) 走完后,b还没有变成新的头结点时候,自旋不就终止了,a不就退出了,即使这样也没有问题,b已经被唤醒了,会继续调用doReleaseShared方法来唤醒下个节点,只是少了a线程的唤醒,效率可能会低一些,并不影响功能。

所以

  if (ws == Node.SIGNAL) {
                	// cas赋值0成功后,就唤醒节点
                    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 }
什么样的情况下才会cas失败,那就是ws值不为0喽,不为0那不是短路了吗,第一个ws== 0条件就过不去,所以只有可能的情况是ws==0成功了,走到cas判断值,有其他线程更改了ws的值。那什么情况下ws的值才会被更改呢,首先得考虑什么情况下ws的值才为0。

  • 第一种,走进第一个if里面,cas将waitstatus值更改为0,那这样就唤醒节点了,不会走到else if下。
  • 第二种,就是队尾节点。上文中曾提到waitstatus的枚举值,也知道队列的特性队尾进,队头出。所以新入队的节点都会重新成为队尾,我们知道节点的默认状态为0,而且shouldParkAfterFailedAcquire 此方法中提到过争取锁失败的时候加入队列中会修改前驱节点的waitstatus值为SIGNAL,所以可以大胆的猜测,当前节点为尾节点,所以waitstatus值等于0,走到cas判断的时候,有新的节点入队,此时新节点调用shouldParkAfterFailedAcquire将前驱节点的waitstatus值更改为 SIGNAL,才会出现这种情况。
 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
             * 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;
    }

达到这种状况条件是很苛刻的:

  • 第一,if (h != null && h != tail) {这个队列至少有两个节点
  • 第二,h的waitstatus==0判断条件满足即是尾节点
  • 第三,走进cas的判断值有节点入队。

总结

关于AQS的介绍大概就到此结束了,本系列详细介绍了AQS的思想和原理,以及独占锁和共享锁的区别,分别介绍了两种在获取锁以及释放锁的差异。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值