AbstractQueuedSynchronizer实现源码解析(二)

来源:互联网 发布:国内外大数据公司 编辑:程序博客网 时间:2024/06/05 08:00

    在前面分析了独占模式获取锁和释放锁的流程,接下来将进行共享模式的获取锁和释放锁的分析。

    这里说明一下,不同线程能够获取共享模式的锁,但此时如果有线程尝试获取独占模式的锁则会失败,同理,当单一线程获取来独占模式的锁之后,此时不允许任何线程获取共享模式的锁。
    关于共享模式获取锁的实现,AQS内部的做法也是把一个标记位共享模式的结点添加到独占模式同一个等待队列中,然后释放的时候同样是头结点的非取消后继结点首先获取锁,不同的是,如果此后继结点的直接后继结点同样也是共享模式的话,则会同时唤醒该其后继结点,形成一个传递唤醒的过程。

具体实现解析

(1)共享模式acquireShared获取锁

    首先,来看看获取锁acquireShared的调用。

    public final void acquireShared(int arg) {        if (tryAcquireShared(arg) < 0)            doAcquireShared(arg);    }
    首先判断tryAcquireShared的值,子类继承tryAcquireShared来判断当前线程能否获得共享锁,要求返回负值表示获取失败;返回0表示当前获取成功,但不允许后续结点获取同样的共享模式的锁;返回大于0的值表示当前获取成功,后续结点也同样可能可以获取成功。因此,如果这里返回负值,则表明获取共享锁失败,需要进入等待队列。
    doAcquireShared函数如下:
 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);                    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);        }    }
    和独占模式类似,函数首先调用addWaiter往等待队列添加一个标记为共享模式的结点,然后类似地,为了防止并发竞争问题,一个循环内判断获取锁(tryAcquireShared),并且获取失败后更改状态(shouldParkAfterFailedAcquire),更改状态后重新判断状态,然后再失败后则会阻塞当前线程(parkAndCheckInterrupt),如果循环过程中失败抛出异常的话,也需要取消当前结点的获取(cancelAcquire)。唯一不同的地方是判断获取锁的条件部分,假设前继结点是head,则会调用tryAcquireShared获取锁,如果返回非负值,则表示获取锁成功,这时便调用setHeadAndPropagate。
    private void setHeadAndPropagate(Node node, int propagate) {        Node h = head;        setHead(node);        if (propagate > 0 || h == null || h.waitStatus < 0) {            Node s = node.next;            if (s == null || s.isShared())                doReleaseShared();        }    }
    首先调用setHead把当前node设置为头结点,然后如果propagate值(也就是tryAcquireShared返回值)大于0,即子类表明可以让后继结点尝试获取锁,或者前head结点的waitStatus < 0,这个判断初看有点奇怪,但事实上是为了判断waitStatus是否为Node.PROPAGATE,因为并发的存在,所以Node.PROPAGATE有可能被替换成Node.SIGNAL(后继结点调用shouldParkAfterFailedAcquire),但只要都判断负值即可,另外按照设计head结点初始化后一定会引用某一结点,这里的h == null应该永不成立。以上条件其中之一为true以后,获取node的next指向的结点,然后判断该结点是否共享模式,如果是则调用doReleaseShared()尝试释放后续结点。
    doReleaseShared调用与释放锁一致,因此在后面解析。

(2)共享模式releaseShared释放锁
    先来看看releaseShared释放锁。
    public final boolean releaseShared(int arg) {        if (tryReleaseShared(arg)) {            doReleaseShared();            return true;        }        return false;    }
    释放锁的逻辑也很简单,同样也是先判断tryReleaseShared能否返回true,表明可以释放锁的时候,则调用doReleaseShared。
    private void doReleaseShared() {        for (;;) {            Node h = head;            if (h != null && h != tail) {                int ws = h.waitStatus;                if (ws == Node.SIGNAL) {                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))                        continue;                    unparkSuccessor(h);                }                else if (ws == 0 &&                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))                    continue;            }            if (h == head)                break;        }    }
    该函数的结构很简单,如果头结点当前waitStatus为SIGNAL,即表明后继结点阻塞了,因此需要唤醒后继结点(unparkSuccessor),同时把头结点状态更改为0;如果头结点waitStatus为0,则尝试更改为PROPAGATE,这样下面后继结点唤醒的时候setHeadAndPropagate就可以继续唤醒后续结点。另外,这里的操作由于会对后继结点唤醒造成影响,因此必须要利用循环重新检查保证CAS成功。

    这样我们便大致剖析了共享模式下获取锁和释放锁的流程,其实对比起之前独占模式的获取与释放,主要的是在获取锁之后,要注意把后继结点里同样是共享模式的结点都一并唤醒,达到一种传递的效果。

(3)其它变种。
    与独占模式类似,共享模式同样有
    acquireSharedInterruptibly
    tryAcquireSharedNanos
    这些变种,但它们的实现都和独占模式类似,也是在原实现基础上添加一些抛出异常以及时间判断。因此这里忽略。

    这样,第二部分的剖析到这里完结,本次我们主要剖析里共享模式下的获取锁和释放锁,下部分集中剖析Condition的实现。
0 0
原创粉丝点击