深入理解AQS(抽象队列同步器)
一、什么是AQS
AQS:用来构建锁或其他同步器组件的重量级基础框架及整个JUC体系的基石,通过内置的FIFO队列来完成资源获取线程的排队工作,并通过一个int类型变量表示持有锁的状态。如果共享资源被占用,就需要一定的阻塞等待唤醒机制来保证锁的分配。主要通过CLH队列的变体实现,将暂时获取不到锁的线程加入到队列中,这个队列就是AQS抽象的表现。它将请求共享资源的线程封装成队列的节点(Node),通过CAS自旋以及LockSupport.park()的方式,维护state变量的状态,使并发达到同步的控制效果。
CLH队列:Craig、Landin and Hagersten队列,是一个单向链表,AQS中的队列是CLH变体的虚拟双向队列,虚拟的双向队列即不存在队列实例,仅存在节点之间的关联关系。
java.util.concurrent.locks.AbstractOenableSynchronizer;
解释:是用来构建锁或者其他同步器组件的重量级基础框架及整个JUC体系的基石,通过内置的FIFO(先进先出)队列来完成资源获取线程的排队工作,并通过一个int类变量表示持有锁的状态
和AQS有关的类:
ReentrantLock:
CountDownLatch
ReentrantReadWriteLock:
Semaphore:
锁和AQS的关系:锁面向锁的使用者,定义了程序员和锁交互的使用层API,隐藏了实现细节
同步器AQS是锁的实现
二、AQS源码分析
AQS使用一个volatile的int类型的成员变量来表示同步状态,通过内置的 FIFO队列来完成资源获取的排队工作将每条要去抢占资源的线程封装成 一个Node节点来实现锁的分配,通过CAS完成对State值的修改。
AQS的同步状态state成员变量 零说明没有资源占用,自由状态可以办理 大于等于1 有线程占用资源
AQS的CLH队列
AQS同步队列的基本结构
AQS底层是怎么排队的 :是用LockSupport.park()来进行排队的
三、通过ReentrantLock中的非公平锁进行分析
主要是通过以下几个方法进行实现的:
通过银行办理业务的例子来深入了解:
三个线程A,B,C来银行窗口办理业务,服务窗口每次只能服务一个人,初识的时候窗口是没人的!
lock()
这边线程A拿到了去窗口办理业务的机会,通过compareAndSetState比较并交换,将AQS中变量state设置成1
acquire()
线程A已经占用了窗口,线程B只能走acquire这个方法
tryAcquire()
在来看acquire() 的方法,B线程返回false,取反的是true,往下面看下一个方法
addWaiter(Node.EXCLUSLVE)
完成B节点的入队,通过enq源码可以知道,B线程第一次并没有进行入队,到第二次循环才将B线程入队返回
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
B抢成功了去去窗口办理业务,抢占不成功就在等待,直到被唤醒。
unlock();
A 办完业务,准备离开,B上位
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话