AQS 原理解读
【摘要】 概述全称是 AbstractQueuedSynchronizer,是阻塞式锁和相关的同步器工具的框架特点: 用 state 属性来表示资源的状态(分独占模式和共享模式),子类需要定义如何维护这个状态,控制如何获取 锁和释放锁getState - 获取 state 状态setState - 设置 state 状态compareAndSetState - cas 机制设置 state 状态独...
概述
全称是 AbstractQueuedSynchronizer,是阻塞式锁和相关的同步器工具的框架
特点: 用 state 属性来表示资源的状态(分独占模式和共享模式),子类需要定义如何维护这个状态,控制如何获取 锁和释放锁
- getState - 获取 state 状态
- setState - 设置 state 状态
- compareAndSetState - cas 机制设置 state 状态
独占模式是只有一个线程能够访问资源,而共享模式可以允许多个线程访问资源 提供了基于 FIFO 的等待队列,类似于 Monitor 的 EntryList
条件变量来实现等待、唤醒机制,支持多个条件变量,类似于 Monitor 的 WaitSet
子类主要实现这样一些方法(默认抛出 UnsupportedOperationException)
- tryAcquire(int arg):独占获取同步状态,获取同步状态成功后,其他线程需要等待该线程释放同步状态才能获取同步状态。
- tryRelease(int arg) 独占式释放同步状态
- tryAcquireShared(int arg) 共享式获取同步状态,返回值大于等于0则表示获取成功,否则获取失败。
- tryReleaseShared(int arg) 共享式释放同步状态。
- isHeldExclusively:当前同步器是否在独占模式下被线程占用,一般该方法表示同步器是否被当前线程独占
获取锁的姿势
释放锁的姿势
手写不可重入锁
自定义同步器
自定义锁
测试
输出
22:29:28.727 c.TestAqs [t1] - locking...
22:29:29.732 c.TestAqs [t1] - unlocking...
22:29:29.732 c.TestAqs [t2] - locking...
22:29:29.732 c.TestAqs [t2] - unlocking...
不可重入测试
如果改为下面代码,会发现自己也会被挡住(只会打印一次 locking)
具体
起源
早期程序员会自己通过一种同步器去实现另一种相近的同步器,例如用可重入锁去实现信号量,或反之。这显然不够优雅,于是在 JSR166(java 规范提案)中创建了 AQS,提供了这种通用的同步器机制。
目标
AQS 要实现的功能目标
- 阻塞版本获取锁 acquire 和非阻塞的版本尝试获取锁 tryAcquire
- 获取锁超时机制
- 通过打断取消机制
- 独占机制及共享机制
- 条件不满足时的等待机制
要实现的性能目标
Instead, the primary performance goal here is scalability: to predictably maintain efficiency even, orespecially, when synchronizers are contended.
设计
AQS 的基本思想其实很简单
获取锁的逻辑
释放锁的逻辑
要点:
原子维护 state 状态
阻塞及恢复线程
维护队列
state 设计
- state 使用 volatile 配合 cas 保证其修改时的原子性
- state 使用了 32bit int 来维护同步状态,因为当时使用 long 在很多平台下测试的结果并不理想
阻塞恢复设计
- 早期的控制线程暂停和恢复的 api 有 suspend 和 resume,但它们是不可用的,因为如果先调用的 resume那么 suspend 将感知不到
- 解决方法是使用 park & unpark 来实现线程的暂停和恢复,先 unpark 再 park 也没问题
- park & unpark 是针对线程的,而不是针对同步器的,因此控制粒度更为精细
- park 线程还可以通过 interrupt 打断
队列设计
- 使用了 FIFO 先入先出队列,并不支持优先级队列
- 设计时借鉴了 CLH 队列,它是一种单向无锁队列
队列中有 head 和 tail 两个指针节点,都用 volatile 修饰配合 cas 使用,每个节点有 state 维护节点状态 入队伪代码,只需要考虑 tail 赋值的原子性,
出队伪代码
为什么 AQS 需要一个虚拟 head 节点
每个节点都必须设置前置节点的 ws 状态为 SIGNAL(-1),因为每个节点在休眠前,都需要将前置节点的 ws 设置成 SIGNAL。否则自己永远无法被唤醒,所以必须要一个前置节点,而这个前置节点,实际上就是当前持有锁的节点。
由于第一个节点他是没有前置节点的,就创建一个假的。
总结下来就是:每个节点都需要设置前置节点的 ws 状态(这个状态为是为了保证数据一致性),而第一个节点是没有前置节点的,所以需要创建一个虚拟节点。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)