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

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

AbstractQueuedSynchronizer
抽象同步队列简称 AQS
,它是实现同步器的基础组件,并发包中的底层就是使用AQS实现的。AQS是一个 FIFO的双向队列
,其内部通过节点 head
tail
记录队首和队尾元素,队列元素类型为 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;
            }
        }
    }
}
复制代码

条件变量的支持

notify
wait
是配合 synchronized
内置锁实现线程间同步的基础设施一样,条件变量的 signal
await
方法也是用来配合锁(使用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.CONDITION
node
节点,然后将该节点插入条件队列末尾,之后当前线程会释放获取的锁(也就是会操作锁对应的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
是并发包下锁的最底层实现,典型的 ReentrantLock
ReentrantReadWriteLock
以及 CountDownLatch
CyclicBarrier
等,都是基于AQS实现的, AQS
核心主要包括 state
、同步队列以及 模板方法模式
对操作队列的算法进行了封装,留下了独占/共享锁的获取/释放由子类去实现,基本思路是子类通过state来完成获取/释放锁的操作。以前觉得并发、锁这些东西很深奥,不可理解,AQS离我也很遥远,但是有幸学习了AQS之后再反观锁的源码,醍醐灌顶,因为写blog文笔较烂,偏向于源码,所以有什么不清楚的可以评论一起讨论学习。关于并发包下的 Lock和synchronized的区别
后面专门梳理一篇blog进行分析和讲解。

原文 

https://juejin.im/post/5effeeec6fb9a07e976bd560

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » Java抽象同步队列AQS原理浅析

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址