美文网首页
AQS 源码分析

AQS 源码分析

作者: 不是明天 | 来源:发表于2018-08-31 11:22 被阅读0次

    1.是什么?

    全称:AbstractQueuedSynchronizer

    抽象的队列式的同步器,AQS定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如常用的ReentrantLock/Semaphore/CountDownLatch...

    2.框架

    数据结构:

          1)维护了一个volatile int state

          2)先进先出的双向链表,头节点为获取锁的线程

    自定义同步器和AQS

          AQS顶层已经实现了等待队列的维护(出队/入队)

          自定义同步器只需要实现共享资源state的获取和释放,自定义同步器主要需要实现的方法包括:

              1)isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。

        2) tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。

      3) tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。

        4)tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。

        5)tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。

    举例说明:

    RetreentLock:state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证state是能回到零态的。

            CountDownLatch:任务分为N个子线程去执行,state也初始化为N(注意N要与线程个数一致)。这N个子线程是并行执行的,每个子线程执行完后countDown()一次,state会CAS减1。等到所有子线程都执行完后(即state=0),会unpark()主调用线程,然后主调用线程就会从await()函数返回,继续后余动作。

            思考:unpark()之后,应该是重新竞争CPU的执行权吧?

    3.源码分析

      按照acquire-release、acquireShared-releaseShared的次序分析AQS顶层源码

      3.1 AbstractQueuedSynchronizer#acquire(int i)

            分析:此方法是独占模式下线程获取共享资源的顶层入口。如果获取到资源,线程直接返回,否则进入等待队列,直到获取到资源为止,且整个过程忽略中断的影响。这也正是lock()的语义,当然不仅仅只限于lock()。获取到资源后,线程就可以去执行其临界区代码了。

              AbstractQueuedSynchronizer#tryAcquire()具体资源的获取交由自定义同步器去实现了(通过state的get/set/CAS),至于能不能重入,能不能加塞,那就看具体的自定义同步器怎么去设计了,自定义同步器在进行资源访问时要考虑线程安全的影响。

                思考:为什么不事先为抽象?

    这里之所以没有定义成abstract,是因为独占模式下只用实现tryAcquire-tryRelease,而共享模式下只用实现tryAcquireShared-tryReleaseShared。如果都定义成abstract,那么每个模式也要去实现另一模式下的接口。说到底,Doug Lea还是站在咱们开发者的角度,尽量减少不必要的工作量。

      AbstractQueuedSynchronizer#addWaiter(Node mode),首先以指定的模式构造节点(共享还是独占),再尝试以cas的方式插入队尾,失败再使用enq方法插入队尾。

    此处对Node进行解释:

        Node结点是对每一个访问同步代码的线程的封装,其包含了需要同步的线程本身以及线程的状态。变量waitStatus则表示当前被封装成Node结点的等待状态:

    CANCELLED:值为1,在同步队列中等待的线程等待超时或被中断,需要从同步队列中取消该Node的结点,其结点的waitStatus为CANCELLED,即结束状态,进入该状态后的结点将不会再变化。

    SIGNAL:值为-1,被标识为该等待唤醒状态的后继结点,当其前继结点的线程释放了同步锁或被取消,将会通知该后继结点的线程执行。说白了,就是处于唤醒状态,只要前继结点释放锁,就会通知标识为SIGNAL状态的后继结点的线程执行。

    CONDITION:值为-2,与Condition相关,该标识的结点处于等待队列中,结点的线程等待在Condition上,当其他线程调用了Condition的signal()方法后,CONDITION状态的结点将从等待队列转移到同步队列中,等待获取同步锁。

    PROPAGATE:值为-3,与共享模式相关,在共享模式中,该状态标识结点的线程处于可运行状态。

    0状态:值为0,代表初始化状态。

    AbstractQueuedSynchronizer#enq(Node node),通过自旋的方式设置头节点或者尾节点。

    AbstractQueuedSynchronizer#acquireQueued(Node node),该线程获取资源失败,已经被放入等待队列尾部了。

              分析:1)如果前驱节点是头节点,则自旋的方式获取共享资源

                        2)否则进入park当前线程waiting,等待被unpark

    安全点:

          是否应该park需要根据AbstractQueuedSynchronizer#shouldParkAfterFailedAcquire(Node pred, Node node)方法判断,该方法会寻找一个安全点,然后再将当前线程挂起。

            步骤分析:

                  前置节点的waitStatus是否为SIGNAL,如果是,则直接挂起;如果不是,继续回溯,直到找到SIGNAL状态的节点或者找到一个waitStatus的节点并CAS的设置为SIGNAL状态。

    *****  释放独占锁

    *****  获取共享锁

    *****  释放共享锁

    *********************************

    readwritelock的实现方式:

    参考:http://www.cnblogs.com/waterystone/p/4920797.html

    相关文章

      网友评论

          本文标题:AQS 源码分析

          本文链接:https://www.haomeiwen.com/subject/eplswftx.html