JUC之AQS源码分析

队列同步器(AbstractQueuedSynchronizer)作为构建锁及其他同步器的基础框架,它通过volatile状态变量及FIFO队列实现线程获取或等待资源的逻辑,JUC中的同步组件(ReentrantLock、ReentrantReadWriteLock、CountDownLatch等)均基于此基础上实现。
AbstractQueuedSynchronizer的源码中,定义如下模板方法:
JUC之AQS源码分析_第1张图片
通过上述定义的模板办法,子类继承AbstractQueuedSynchronizer之后,根据特定需求,实现模板方法中调用的抽象方法,即可定制相应的同步框架,相应的抽象方法如下:
JUC之AQS源码分析_第2张图片

在AQS的模型中,将线程包装为Node(节点),节点的重要成员变量说明:
JUC之AQS源码分析_第3张图片

waitStatus值: =1节点设置为取消(超时或中断);
=-1后继节点处于等待
=-2节点位于等待队列
=-3下次获取共享式同步状态被传播
接下来,逐步分析AQS定义的同步方法

一、独占锁的获取与是释放

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

调用抽象tryAcquire(arg)尝试获取资源,获取失败则执行addWaiter(Node.EXCLUSIVE)将线程加入FIFO队列

private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);//以独占模式封装线程的Node节点
    Node pred = tail;//记录尾节点
    if (pred != null) {//尾节点存在
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {//CAS将node设置为尾节点
            pred.next = node;
            return node;
        }
    }
    enq(node);//尾节点不存在或CAS node尾尾节点失败
    return node;
}
private Node enq(final Node node) {
    for (;;) { 
        Node t = tail;//获取当前为节点
        if (t == null) { // 队列为空,构建头节点
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {// CAS将节点设置为尾节点
                t.next = node;
                return t;
            }
        }
    }
}

将node添加至同步对列之后,执行acquireQueued(final Node node, int arg)线程进入自旋状态,,每个节点都在观察,当条件满足时,获取同步状态;否则,节点阻塞

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

综上分析,独占式同步状态获取流程如下图:
JUC之AQS源码分析_第4张图片
独占式锁的释放,无多线程竞争,逻辑相当简单,只需调用抽象方法,并唤醒后继节

public final boolean release(int arg) {
    if (tryRelease(arg)) {//调用抽象方法
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);//唤醒后继节点的阻塞线程
        return true;
    }
    return false;
}

二、共享锁的获取与释放

public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)//获取失败,自旋获取同步状态
        doAcquireShared(arg);
}

在共享式获取中,先尝试调用抽象方法获取锁,失败,则自旋获取

private void doAcquireShared(int arg) {
    final Node node = addWaiter(Node.SHARED);//接入FIFO同步队列
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();//前驱节点
            if (p == head) {//前驱节点为头节点
                int r = tryAcquireShared(arg);
                if (r >= 0) {//>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);
    }
}

共享式获取流程与独占式获取大体一致,只是具体获取同步状态的规则不一致(由抽象方法实现),然而共享式释放同步资源则与独占释放不那么一样(主要在于共享式释放存在多线程竞争)

public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {//抽象方法获取资源
        doReleaseShared();//
        return true;
    }
    return false;
}
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;            // 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;
    }
}

无论是共享式还是独占式获取,线程自旋观察过程中是否阻塞if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())由此实现

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)//前驱节点状态=-1需要阻塞
        return true;
    if (ws > 0) {//节点因中断或超时被取消CACLLED
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);//跳过被取消的节点
        pred.next = node;
    } else {
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);//设置有效前节点状态
    }
    return false;
}

在shouldParkAfterFailedAcquire根据前节点状态来判断是否需要阻塞当前线程,然后执行parkAndCheckInterrupt()

private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);//阻塞当前线程
    return Thread.interrupted();//返回中断状态
}

共享式与独占式获取同步状态的流程以介绍未必,至于对应超时,及中断响应获取的状态的流程与上大体一致,只是在定义模板方法方法中添加了响应中断、超时等业务,具体并在一一分析。

你可能感兴趣的:(java)