今天清明节放假正好趁此总结一下AQS的源码分析😊😊😊
AbstractQueuedSynchronizer 的简称,Doug Lea大师编写的AQS源码,其实就是对CAS的封装和丰富,用于实现同步器(如锁和信号量等)的基础框架
其实就是一个通用的同步框架,设置这样一个框架要满足以下几点:
- 通用性:在实现底层必要的同步机制后,给上层调用留有空间,也就是保持对外接口的简单性和纯粹性
- 利用CAS,原子的修改共享标志位
- 等待队列:让队列的头部进行自旋获取标志位,而其他的线程则挂起
AQS的一个成员变量state,就是来判断共享资源是否被占用的标志位,设置为int类型是为了记录重入的次数,还有就是共享模式下表示线程的数量
然后它还有一个表示队列头尾的head、tail指针,这两个指针维护了一个FIFO的双向链表来对线程进行管理
java
1 private transient volatile Node head;
2 private transient volatile Node tail;
队列中的每一个节点Node其实就是对线程的封装,其中也包括四种线程在队列中的等待状态,然后Node中方法也很简单,其中有一个就是获取前置Node
队列中的节点有两种模式,共享和独占,我说一下独占把,ReentrantLock底层就是基于独占模式的AQS来实现的
我们设计这样一个框架其实有两种主要的使用场景:
- 尝试获得锁,不管有没有获得立即返回
- 必须获得锁,如果当前锁被占用,则进行等待
源码中的tyrAcquire和acquire正好对应了这两种场景:

tyrAcquire
在AQS中是直接抛出异常,是为了让使用的上层自己实现,因为在尝试获得锁的过程中可能包含业务自定义的逻辑,比如是否可重入
acquire
方法是重要的,acquireQueued(addWaiter(Node.EXCLUSIVE),arg)

addWaiter(Node.EXCLUSIVE),arg
方法是将当前线程封装为一个Node,然后加入等待队列,因为是符合FIFO的,所以通过CAS操作将Node添加到队尾
这里它采用了快速插入的机制,就是一开始先假设并发量比较少(就是前置节点不为空)的情况下CAS进行插入,如果插入失败,则执行enq(node);
方法来循环尝试插入

在队列中插入成功后返回一个Node,接下来就是acquireQueued方法

它首先去拿当前节点的前置节点,如果前置节点为head,说明是队首然后当前节点有权限来尝试获得锁,如果不是队首,然后看当前线程是否需要挂起等待,如果需要就挂起等待,如果不需要则继续循环判断是否有权限来尝试获得锁
当前线程是否需要挂起等待的逻辑:

它判断它的前置节点是否也在等待获得锁,如果是,则当前这个线程就挂起等待,如果当前线程是取消状态,则从队列中取消该节点
如果判断当前节点需要被挂起,则执行下面方法:

就是将当前线程挂起阻塞在这,一直等到持有锁的线程来调用relese方法来唤醒
大量线程挂起,然后得有relese释放,来看一下relese的释放:

和tryAcquire一样,它的tryRelese也是交给上层去实现
主要是relese方法,来看unparkSuccessor方法是怎么释放的:


它是从队尾依次向前搜索,找到head后面最靠前的并且是非CANCELED的节点来唤醒,调用unpark
从后往前搜索是为了避免在多线程下新建Node节点时没有与后面节点建立Next连接导致搜索中断