转载

Java抽象同步队列AQS原理浅析

关注可以查看更多粉丝专享blog~

AbstractQueuedSynchronizer 抽象同步队列简称 AQS ,它是实现同步器的基础组件,并发包中锁的底层就是使用AQS实现的。AQS是一个 FIFO的双向队列 ,其内部通过节点 headtail 记录队首和队尾元素,队列元素类型为 Node 。AQS采用 模板方法模式 ,父类抽象出通用模板, 将方法延迟到子类加载

独占方式

acquire(int arg)

当一个线程调用 acquire(int arg) 获取独占资源时,会首先使用 tryAcquire 方法尝试获取资源,具体是设置 state 值,成功则直接返回,失败则将当前线程封装为类型为 Note.EXCLUSIVE的Node 节点后插入到AQS阻塞队列的尾部,并调用 LockSupport.park(this) 方法挂起自己。

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

release(int arg)

当一个线程 release(int arg) 方法时会尝试使用 tryRelease 操作释放资源,这里是设置状态变量 state 的值,然后调用 LockSupport.unpark(thread) 方法激活AQS队列里面被阻塞的一个线程(thread)。被激活的线程则使用 tryAcquire 尝试,看当前状态变量state的值是否能满足自己的需要,满足则该线程被激活,然后继续执行,否则还是会被放入AQS队列并被挂起。

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}
复制代码

共享方式

acquireShared(int arg)

当线程调用 acquireShared(int arg) 获取共享资源时,会首先使用 tryAcquireShared 尝试获取资源,具体是设置状态变量 state 的值,成功则直接返回,失败则将当前线程封装为类型为 Note.SHARED 的Node节点后插入到AQS阻塞队列的尾部,并使用LockSupport.park(this)方法挂起自己。

public final void acquireShared(int arg) {
   if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}
复制代码

releaseShared(int arg)

当一个线程调用 releaseShared(int arg) 时会尝试使用 tryReleaseShared 操作释放资源,这里是设置状态变量 state 的值,然后使用 LockSupport.unpark(thread) 激活AQS队列里面被阻塞的一个线程(thread)。被激活的线程则使用tryReleaseShare查看当前状态变量state的值是否能满足自己的需要,满足则该线程被激活,然后继续向下执行,否则还是会被放入AQS队列并被挂起。

public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}
复制代码

入队操作

入队时会先判断,是否 需要初始化 ,如果队尾指针指向null,则进行初始化,创建哨兵节点,首尾都指向哨兵节点。如果已经存在了,则将node的前置节点指向t,然后tail节点指向新node元素,前尾结点t的后置节点指向node,最后返回。

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;
            }
        }
    }
}
复制代码

条件变量的支持

notifywait 是配合 synchronized 内置锁实现线程间同步的基础设施一样,条件变量的 signalawait 方法也是用来配合锁(使用AQS实现的锁)实现线程间同步的基础设施。如我们经常用到的 LinkedBlockingQueue ,采用了 ReentrantLock 和条件队列,当进行写入的时候如果队列没有满,则唤醒在 notFull条件队列 中等待的线程继续写入;当进行取出的时候如果队列中任有数据可以消费,则唤醒 notEmpty条件队列 中等待的线程继续消费。

/** Lock held by take, poll, etc */
private final ReentrantLock takeLock = new ReentrantLock();

/** Wait queue for waiting takes */
private final Condition notEmpty = takeLock.newCondition();

/** Lock held by put, offer, etc */
private final ReentrantLock putLock = new ReentrantLock();

/** Wait queue for waiting puts */
private final Condition notFull = putLock.newCondition();
复制代码

当线程调用条件变量 await() 方法时(必须先调用 lock() 方法获取锁),在内部会构造一个类型为 Node.CONDITIONnode 节点,然后将该节点插入条件队列末尾,之后当前线程会释放获取的锁(也就是会操作锁对应的state变量的值),并被阻塞挂起。

public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    Node node = addConditionWaiter();
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    while (!isOnSyncQueue(node)) {
        LockSupport.park(this);
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}
复制代码

总结

AQS 是并发包下锁的最底层实现,典型的 ReentrantLockReentrantReadWriteLock 以及 CountDownLatchCyclicBarrier 等,都是基于AQS实现的, AQS 核心主要包括 state 、同步队列以及 模板方法模式 对操作队列的算法进行了封装,留下了独占/共享锁的获取/释放由子类去实现,基本思路是子类通过state来完成获取/释放锁的操作。以前觉得并发、锁这些东西很深奥,不可理解,AQS离我也很遥远,但是有幸学习了AQS之后再反观锁的源码,醍醐灌顶,因为写blog文笔较烂,偏向于源码,所以有什么不清楚的可以评论一起讨论学习。关于并发包下的 Lock和synchronized的区别 后面专门梳理一篇blog进行分析和讲解。

原文  https://juejin.im/post/5effeeec6fb9a07e976bd560
正文到此结束
Loading...