AQS源码分析
AQS(队列同步器)源码分析
AQS源码详细解读 - 知乎 (zhihu.com)
JavaGuide
实现类:ReentrantLock、CountDownLatch、CyclicBarrier、Semaphore
- AQS重要成员变量(state)
- 内部类-Node(等待队列的实现)
- 获取资源(acquire)源码
- 释放资源(release)源码
- 内部类-ConditionObject(条件队列的实现)
AQS的工作模式分为独占模式和共享模式,记录在节点的信息中。
使用模板方式 => 定义一个操作中算法的骨架
一般实现类只实现其中一种模式:ReentrantLock就实现了独占模式、(例外)ReentrantReadAndWriteLock实现了独占模式和共享模式
为什么线程节点成为了头节点为什么会将节点中的线程信息置为空?
- 首先AQS继承了AbstractOwnableSynchronied这个类,这个类就是用来记录当前正在运行的线程的
Node节点
获取资源
本质上都是对state
这个资源变量的修改
acquire()方法
addWaiter()方法
将当前线程插入至队尾,返回在等待队列中的节点(就是处理了它的前驱后继)
enq()方法
将节点插入队尾,失败则自旋,直到成功
总结addWaiter()方法
总的来说,addWaiter负责的就是将tryAcquire()方法获取锁失败的线程插入到队列中
acquireQueued()方法
自旋方式获取资源并判断是否需要被挂起(原则上除了唤醒和中断会一直卡在这个方法,即便是挂起状态)
setHead()方法
shouldParkAfterFailedAcquire()方法
判断当前节点是否应该被挂起
parkAndCheckInterrupt()方法
将对应的线程挂起
若确定有必要park,才会执行此方法
selfInterrupt()方法
对当前线程产生一个中断请求。能走到这个方法,说明acquireQueued()返回true,就进行自我中断
acquire的步骤
- tryAcquire()尝试获取资源
- 如果获取失败,则通过addWaiter(Node.EXCLUSIVE), arg)方法把当前线程添加到等待队列队尾,并标记为独占模式
- 插入等待队列后,并没有放弃获取资源,acquireQueued()自旋尝试获取资源。根据前置节点状态状态判断是否应该继续获取资源。如果前驱是头结点,继续尝试获取资源
- 在每一次自旋获取资源过程中,失败后调用shouldParkAfterFailedAcquire(Node, Node)检测当前节点是否应该park()。若返回true,则调用parkAndCheckInterrupt()中断当前节点中的线程。若返回false,则接着自旋获取资源。当acquireQueued(Node,int)返回true,则将当前线程中断;false则说明拿到资源了
- 在进行是否需要挂起的判断中,如果前置节点是SIGNAL状态,就挂起,返回true。如果前置节点状态为CANCELLED,就一直往前找,直到找到最近的一个处于正常等待状态的节点,并排在它后面,返回false,acquireQueed()接着自旋尝试,回到3)
- 前置节点处于其他状态,利用CAS将前置节点状态置为SIGNAL。当前置节点刚释放资源,状态就不是SIGNAL了,导致失败,返回false。但凡返回false,就导致acquireQueed()接着自旋尝试
- 最终当tryAcquire(int)返回false,acquireQueued(Node,int)返回true,调用selfInterrupt(),中断当前线程
释放资源
首先调用子类的tryRelease()方法释放锁,然后唤醒后继节点,在唤醒的过程中,需要判断后继节点是否满足情况,如果后继节点不为空且不是作废状态,则唤醒这个后继节点,否则从tail节点向前寻找合适的节点,如果找到,则唤醒
为什么从后继节点作废时候要从tail开始找?
unparkSuccessor方法
高并发下的入队逻辑
原子性问题
在该段方法中,将当前节点置于尾部使用了CAS来保证线程安全,但是请注意:在if语句块中的代码并没有使用任何手段来保证线程安全!
也就是说,在高并发情况下,可能会出现这种情况:
线程A通过CAS进入if语句块之后,发生上下文切换,此时线程B同样执行了该方法,并且执行完毕。然后线程C调用了unparkSuccessor方法
假如是从头到尾的遍历形式,线程A的next指针此时还是null!也就是说,会出现后续节点被漏掉的情况
node.prev = t
先于CAS执行,也就是说,你在将当前节点置为尾部之前就已经把前驱节点赋值了,自然不会出现prev=null的情况
release()方法
首先调用子类的tryRelease()方法释放锁,然后唤醒后继节点,在唤醒的过程中,需要判断后继节点是否满足情况,如果后继节点不为空且不是作废状态,则唤醒这个后继节点,否则从tail节点向前寻找合适的节点,如果找到,则唤醒
tryrelease()方法 - ReentrantLock类中的方法
unparkSuccessor()方法
尝试找到下一位继承人,就是确定下一个获取资源的线程,唤醒指定节点的后继节点
ReentrantLock源码分析
内部类Sync的实现
NonfairSync类的实现
FairSync类的实现
构造函数源码
lock()方法源码
unlock()方法源码
__EOF__

本文链接:https://www.cnblogs.com/TPureZY/p/15944058.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)