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的实现方式:
网友评论