AQS源码
1.三种线程调度方式
-
wait()/notify()/notifyAll()
-
await()/signal()/signalAll()
-
LockSupport.park()/LockSupport.unpark()
用来创建锁和其他同步类的基本线程阻塞原语。类似于信号量
Semaphore
,但是只有一个许可,且可以先释放再获取许可。
2.CAS
全称Compare And Swap
,比较并交换。
通过sun.misc.unsafe
类实现,主要参数有三个,分别是内存值、期望值以及更新值,只有内存值和期望值一致时才会将内存值修改为更新值。
例如JUC包下的atomicXXX等类均通过CAS的方式实现。
3.volitale
-
Java内存模型(JMM)
-
volitale
- 共享变量可见性
- 防止指令重排
4.AQS
4.1 简介
AQS全称是AbstractQueuedSynchronizer
,抽象的队列同步器,提供了一个实现阻塞锁(ReetrentLock
)或者相关的同步器(semaphore
、countDownLatch
)的框架,这些锁或者同步器都依赖于一个FIFO的等待队列,并且可以通过一个int
类型的变量来表示锁或者同步器的状态。
AQS定义了2种资源共享模式:
- 独占模式(Exclusive):独占,只有一个线程能执行,如
ReentrantLock
- 共享模式(Share):共享,多个线程可同时执行,如
ReadWriteLock
、Semaphore
等
4.2 源码分析
4.2.1 state
表示锁或者同步器的状态,不同的锁或者同步器对于state的值可能都不同,且各个值表示的含义也不同。
state
使用volatile
关键字修饰,能够保证state的可见性,state
的访问方式有三种:
state不同的值表示不同的锁状态,通常使用cas的方式设置state
的值,
比如ReetrantLock
中state>0
表示锁已经被抢占,其他线程只能进入队列等待唤醒,state=0
表示占用锁的线程已经释放锁了,其他线程可以进行抢占。
再比如Semephore
中state
的初始值就是设置的许可数量,每使用一个许可就将state
值减1,当state=0
时表示许可用完,后续线程就需要排队。
4.2.2 Node
竞争资源失败的线程需要进入等待队列(CLH队列),在进入等待队列之前被封装成一个个的Node
,Node
主要包含以下几个属性:
其中表示节点状态的waitStatus
属性的值为以下几种,新创建的节点waitstatus
的值是0.
waitStatus
<0时表示线程还在有效等待,但是waitStatus
>0时表示timeout或者线程中断,因此源码中很多地方使用>0或者<0的方式进行判断,并没有详细到每一个值的判断。
4.2.3 CLH队列
4.2.3.1 原始CLH队列
CLH队列全称是Craig, Landin, Hagersten lock queue
,是一个基于(隐式)单向链表的可扩展、高性能、公平的自旋锁,能够保证无饥饿,并且先到先得的公平性,通过在后继节点中自旋前继节点中的属性值来实现。
代码示例:
4.2.3.2 变体CLH队列
AQS中使用的CLH队列是原始CLH队列的变体,变体的CLH队列借鉴了CLH队列的思想,即在前置节点中保存一些线程的信息,使用Node
节点中的status
来追踪线程是否应该被阻塞,但是status
的值不能用来控制线程是否应该被锁定。
对于非公平锁,一个线程处于队列的头结点处,但是并不一定能获取锁成功,处于头节点处只是给了一个竞争的权利,如果竞争失败,还是要继续等待。
4.2.4 流程图
4.2.5 源码分析
以ReentrantLock
为例,ReentrantLock
是独占锁,且分为公平锁和非公平锁,本次分析以非公平锁为例。
4.2.5.0 加锁过程分析
lock()
过程
当出现竞争时,调用AQS.acquire(1)
方法
先看AQS中的tryAcquire
方法
是一个由protected
修饰的方法,并且直接抛出异常,这样的方法可以由子类重载,实现具体的逻辑,ReentrantLock.NonfairSync
中的重载方法如下
获取锁失败,!tryAcquire(arg)=true
,执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
方法将当前线程加入到等待队列中。
先看addWaiter(Node.EXCLUSIVE)
方法,设置Node
节点的类型是独占型(Node.EXCLUSIVE
)
继续查看enq(node)
方法
Node
节点入队完成后,执行acquireQueued()
方法,具体源码如下
先看下shouldParkAfterFailedAcquire
方法
一个新的节点,至少要经历两次自旋,shouldParkAfterFailedAcquire
方法才会返回true
,此时就会进入parkAndCheckInterrupt
方法
由于LockSupport.park(obj)
方法不响应中断异常,因此acquireQueued()
方法中只有在由子类实现的tryAcquire
方法抛出异常时才会执行取消节点等待的cancelAcquire
方法
最后再回到acquire()
方法
tryAcquire
表示尝试去获取锁,acquireQueued
表示在获取锁失败后入队等待,如果当前线程获取锁失败,并且在入队等待过程中被中断,那么就会执行selfInterrupt
进行自我中断
此处只是设置了线程的中断状态,中断之后需要如何操作需要在线程中对应编码
4.2.5.1 加锁过程总结
加锁过程中最重要的一个方法就是 tryAcquire
,需要子类自行实现,并且要设置好state的值对应的状态,其余的入队唤醒等操作在aqs中都已经进行了实现,子类不需要再关心。
4.2.5.2 释放过程分析
释放锁的入口是unlock
sync
是ReentrantLock
的内部类,继承了AQS类,release
是定义在AQS中的方法
根据tryRelease
的返回值来确定释放锁是否成功,tryRelease
也是一个需要子类重载的方法,AQS中的原始方法如下
ReentrantLock
中重载的tryRelease
方法
释放锁之后,需要判断队列中是否有线程在等待唤醒,如果有的话,需要执行unparkSuccessor
方法唤醒后继节点
4.2.5.3 释放过程总结
release
是独占模式下释放锁的顶层入口,能够释放指定数量的资源(state每次减少的值),在资源完全释放(state=0)时表示当前线程已经完全释放了锁,如果有后续线程需要唤醒就会去唤醒对应的线程。
4.3 应用
工具类 | 锁类型 | state作用 |
---|---|---|
Semephore | 共享锁 | 初始化许可数量 |
CountDownLatch | 共享锁 | 维护计数器 |
ReentrantLock | 独占锁 | 锁重入次数 |
ReentrantReadWriteLock | 读锁:共享,写锁:独占 | 高16位表示共享锁数量,低16位表示独占锁重入次数 |
CyclicBarrier | 共享锁 | 维护计数器 |
4.4 总结
不同的自定义同步器争用共享资源的方式不同,自定义同步器在实现时只需要定义好state
的获取和释放方式即可,具体的线程等待队列的维护AQS已经在顶层实现好了,自定义同步器在定义时其实只需要根据需要实现以下几个方法即可:
tryAcquire(int)
: 独占模式获取资源,成功返回true
,失败返回false
;tryRelease(int)
: 独占模式释放资源,成功返回true
,失败返回false
;tryAcquireShared(int)
: 共享模式获取资源;tryReleaseShared(int)
: 共享模式释放资源;
参考文档:
1.java并发之AQS详解
2.CLH lock queue的原理解释及Java实现
__EOF__

本文链接:https://www.cnblogs.com/ybyn/p/15118644.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:编写不易,转载请注明出处
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)