AQS
2020年12月8日
14:29
目录 |
- - 1. AQS是一个构建锁的框架。 - 2. AQS中的数据结构 - 3. AQS设计模式 - 4. 从ReentrantLock的加锁过程来理解AQS 概述 重要引用 ''' 总而言之,上述除了 tryAcquire,是由RL定义的,其他都是AQS提供,AQS的获取资源,否则进入的逻辑即是acquire,它包含以下逻辑。
''' |
1. AQS是一个构建锁的框架。
其原理是基于CAS + 阻塞队列 —— 尝试获取,获取不到则进入队列等待。
AQS是区别于synchronized关键字的另一种锁实现机制,历史是,以前synchronized用mutex重量级互斥的悲观锁开销太大,因而Doug Lea大佬开发出基于CAS乐观锁的AQS。但后来synchronized进行了大量改进,例如四种级别的锁,现在它的效率通常来讲是最高的。
常用的AQS的锁有ReentrantLock,Semaphore。
2. AQS中的数据结构
想知道AQS如何利用简单CAS实现线程安全,先来看看它的数据结构,即挂载线程的节点Node和队列。
队列是类似双向链表的CLH链表,节点是普通的节点。
static final class Node { static final Node EXCLUSIVE = null;
static final int CANCELLED = 1; // waitStatus的值,表示后继结点(对应的线程)需要被唤醒 static final int SIGNAL = -1; // waitStatus的值,表示该结点(对应的线程)在等待某一条件 static final int CONDITION = -2; /*waitStatus的值,表示有资源可用,新head结点需要继续唤醒后继结点(共享模式下,多线程并发释放资源,而head唤醒其后继结点后,需要把多出来的资源留给后面的结点;设置新的head结点时,会继续唤醒其后继结点)*/ static final int PROPAGATE = -3; // 等待状态,取值范围,-3,-2,-1,0,1
volatile int waitStatus;
volatile Node prev; // 前驱结点 volatile Node next; // 后继结点 volatile Thread thread; // 结点对应的线程 Node(Thread thread, Node mode) { // Used by addWaiter this.nextWaiter = mode; this.thread = thread; } } |
队列是这个样子的。
3. AQS设计模式
模板设计模式,如下图,其实也非常简单——抽象类大部分东西给你写好,流程写好,剩下一些方法的不同需要你去实现。
对于AQS,实现AQS,需要覆盖的方法有:
- tryAcquire(int):尝试使用CAS获取资源,资源由AQS中的标记变量标记,获取失败则入队,但是入队部分不需要我们实现。
- tryRelease(int):释放资源。
其实上面只是独占式的锁——与之对应还有共享式,区别就是资源是一份还是多份,多人能不能同时使用资源(比如多人读就可以)。
4. 从ReentrantLock的加锁过程来理解AQS
ReentrantLock(RL)是最常用的独占锁,来看它的加锁过程。
- RL调用lock方法,lock调用几个内部类的lock(实现AQS),最后调用父类的acquire方法
- AQS: acquire方法,获取资源,获取不到则进入队列
- 源码,核心代码
- 首先调用RL: tryAcquire方法,尝试获取资源,由RL实现
- 由该方法实现CAS修改AQS的资源量,首先它检查state,如果可以获取,则直接尝试用CAS获取,获取不到则return false,回到acquire
- 该方法判断过程,即使多个进程同时CAS,只有一个能获得,其他会返回fasle,这也是实现独占资源的核心
- addWaiter进入队列
- 这里细节不重要,是入队指针的赋值,重要的只有注释部分,CAS入队以及enq自旋
- enq的代码非常简单,创建或者循环入队
- AQS: addWaiter入队以后返回node给aquireQueued
- AQS: aquireQueued,给队列中排前面的节点(线程)分配资源
- 看注释其中shouldParkAfterFailedAcquire的作用如注释所示
- aquireQueued一般情况下分配完成会return false
总而言之,上述除了 tryAcquire,是由RL定义的,其他都是AQS提供,AQS的获取资源,否则进入的逻辑即是acquire,它包含以下逻辑。
- tryAcquire,需要实现,CAS申请资源
- addWaiter,获取不到资源入队,不需要实现,注意入队也是CAS
- acquireQueued,为队内资源分配资源,并且挂起活动的等待队列,去掉无效节点