AQS 抽象队列同步器详解
同步锁的本质是什么
同步的方法:独享-单个排队窗口,共享-多个排队窗口
抢锁的方法:不公平-插队抢,公平-先来后到排队
没抢到锁怎么办:反复询问窗口是否有空(CAS 自旋),等待叫号(阻塞等待)
唤醒阻塞线程:全部通知,通知下一个(叫号器)
什么是 AQS
AQS 全称 AbstractQueuedSynchronizer 被认为是 J.U.C 的核心,和 CAS 共同撑起了整个 J.U.C 包(java.util.concurrent)
J.U.C 中的大多数同步器都是基于 AQS 来构建的.
抽象队列同步器 AQS,用于解决的就是多线程并发访问控制问题,在传统的多线程编程中,如果有多个线程需要访问同一个变量,就需要使用 sychronized 来为临界值区加锁,但是这种方法即不优雅,也不高效,更重要的是,不能实现更细粒度的控制,这时候 AQS 提供了一种简洁优雅的机制来实现线程安全:管理同步状态、阻塞/唤醒线程、管理等待队列
本质上来说,AQS 是构建(包括锁在内)大部分同步组件的基本基础框架,它实现了对资源的占用,释放;线程的等待唤醒,等相关接口的实现。
比如 ReentrantLock、CountDownLatch、CyclicBarrier 等同步起, 其实都是通过内部类实现了 AQS 框架暴露的 API,以此实现各类同步器功能,这些同步器的主要区别其实就是对同步状态(synchronion state)的定义不同。
AQS 的作用
AQS 框架, 分裂了构建同步器时的一系列关注点,它的额所有操作都围绕着资源——同步状态(synchronization state)来展开,并且解决了如下问题
- 资源时可以被同时访问? 还是在同一时间只能被一个线程访问(共享/独占功能)
- 访问资源的线程如何进行并发管理?(等待队列)
- 如果线程等不及资源了,如何从等待队列退出(超时/中断)
这相当于时模板方法设计模式, AQS 作为父类 定义好框架和内部操作细节, 具体的实现则由子类来操作
AQS 框架将剩下的一个问题留给用户:
什么是资源? 如何定义资源是否可以被访问?
以下则是具体同步器
综上所述:
AQS 提供了一套模板框架:
由于并发的存在,需要考虑的情况很多,因此能否以一种相对简单的方法来完成这两个目标就非常重要, 因为对于用户(AQS 框架的使用者),很多时候并不关心内部复杂的细节。而 AQS 其实就是利用模板方法来实现这一点,AQS 中大多数方法都是 final 或者 private 的。
AQS 通过暴露以下 API 来让用户自己解决上面提到的“什么是资源? 如何定义资源是否可以被访问?”的问题。
AQS 支持中断、超时:
使用了 AQS 框架的同步器,都支持下面的操作:
阻塞和非阻塞(例如 tryLock)同步;
可选的超时设置,让调用者可以放弃等待;
可中断的阻塞操作。
AQS 支持独占模式和共享模式:
AQS 框架内部通过一个内部类 ConditionObject,实现了 Condition 接口,以此来为子类提供条件等待的功能。
AQS 原理简述
AQS 所有操作都围绕着资源——同步状态(synchronization state)来展开,围绕着资源,衍生出三个基本问题:
- 同步状态(synchronization state)的管理
- 阻塞/唤醒线程的操作
- 线程等待队列的管理
AQS 内部主体
属性
state:同步状态位,锁的数量,加锁+1,释放-1
owner:资源拥有者
node:锁的等待者
方法
acquire,acquireShared:定义了资源争用逻辑,如果没拿到,进入等待队列
tryAcquire,tryAcquireShared:实际占用资源的操作, 需要具体使用者来实现
release,releaseShared:定义了释放资源的逻辑,释放之后,通知队列进行争抢
tryRelease,tryReleaseShared:实际执行资源释放的操作,由具体使用者来实现