当前位置: 首页 > news >正文

AQS的理解

目录

  • AQS的理解
    • 1.什么是AQS
    • 2.总体流程
    • 3.注意:

AQS的理解

1.什么是AQS

AQS(AbstractQueuedSynchronizer) 是 Java 并发包(JUC)的核心框架,像 ReentrantLock 就是基于它实现的。它内部有两个核心部分:
一是 volatile 的 int 类型 state 变量(比如 ReentrantLock 中用来记录锁的重入次数);
二是 CLH 双向队列,用来存放竞争锁失败的线程,按 FIFO 排队。
当队头线程被唤醒后,看到 state 为 0 就会通过 CAS 改状态抢锁;
如果是非公平锁,新线程可能插队直接改 state,公平锁则严格按队列顺序。

2.总体流程

图示:
在这里插入图片描述

一、流程拆解(非公平锁完整逻辑):
我们按流程图一步步走,核心看 “两次抢锁” 何时发生、为何必要:

  1. 第一次抢锁(最上方的 CAS)
    触发条件:线程调用 lock(),直接进入 nonfairTryAcquire(非公平抢锁逻辑)。
    操作:尝试用 CAS 将 state 从 0 改为 1(完全插队,不检查队列)。
    结果:
    成功:直接获取锁(setExclusiveOwnerThread 标记当前线程),流程结束。
    失败:进入第二次抢锁。
  2. 第二次抢锁(currentThread == exclusiveThread 判断)
    触发条件:第一次 CAS 失败(state != 0),但可能是 锁重入场景(当前线程已经持有锁)。
    操作:判断 currentThread == exclusiveOwnerThread(当前线程是否是锁的持有者):
    是:说明是 锁重入,直接执行 setState(state += 1),流程结束。
    否:说明锁被其他线程持有,抢锁失败,进入 入队流程。
  3. 入队流程(addWaiter + acquireQueued)
    触发条件:两次抢锁都失败(既没插队成功,也不是锁重入)。
    操作:
    addWaiter:将当前线程包装成 Node,加入 CLH 队列尾部。
    acquireQueued:线程进入队列后,会 循环抢锁(再次调用 tryAcquire),若失败则 park 阻塞,直到被唤醒。

二、“两次抢锁” 的核心设计意图

  1. 第一次抢锁:极致性能优化
    目的:让线程尽可能 “插队” 获取锁,避免进入队列的开销(入队 → 阻塞 → 唤醒的成本很高)。
    典型场景:锁刚被释放(state 变为 0)时,新线程可以直接 CAS 抢占,无需等待队列中的线程被唤醒。
  2. 第二次抢锁:支持 “锁重入”
    目的:处理 同一线程多次获取锁 的场景(比如递归调用 lock())。
    典型场景:
    java
    运行
    lock.lock();
    try {
    // 同一线程再次获取锁(重入)
    lock.lock();
    // …
    } finally {
    lock.unlock(); // state -= 1
    lock.unlock(); // state -= 1 → 最终释放
    }

第二次抢锁时,currentThread == exclusiveOwnerThread 为 true,直接增加 state,实现 锁重入。
3. 入队流程:保证公平性兜底
目的:当线程既没插队成功,也不是锁重入时,必须进入队列 排队等待,保证最终能获取锁(遵循 FIFO 原则)。

3.注意:

进入队列阻塞的线程都要排队(FIFO)获取锁,在队头的线程先看到state为0了,它就去改状态值为1(也就是去抢锁),具体来说就是:

进入队列的线程会按 FIFO 排队,队头线程被唤醒后(也就是持有锁的线程执行完逻辑代码调用unlock()方法解锁后),会先检查 state 是否为 0,然后通过 CAS 抢锁(改 state 为 1)。成功则获取锁并出队,失败则重新阻塞。非公平锁可能有新线程插队,公平锁则严格按队列顺序抢锁。

http://www.dtcms.com/a/327673.html

相关文章:

  • B树索引和B+树索引有什么区别?
  • 编译 BusyBox for ARM 平台
  • 数据结构:图
  • 1、正则表达式入门
  • (LeetCode 每日一题) 2787. 将一个数字表示成幂的和的方案数(动态规划dp+01背包)
  • Python 常用的正则表达式
  • CodeRush AI 助手进驻 Visual Studio:AiGen/AiFind 亮相(五)
  • RL推理的尽头,是熵坍缩?统一SFT与强化学习的新视角
  • 零基础学Java第七讲---调试(IDEA)
  • 面试经典150题[001]:合并两个有序数组(LeetCode 88)
  • 【代码随想录day 17】 力扣 98.验证二叉搜索树
  • iis无法访问文件
  • NTP常见日志分析
  • 每日五个pyecharts可视化图表-line:从入门到精通 (4)
  • 多轮问答与指代消解
  • 测试匠谈 | AI语音合成之大模型性能优化实践
  • @JsonAnyGetter 动态表格渲染的“神”
  • 「机器学习」:金融风控贷款违约预测,天池比赛解决详细思路
  • Redis面试精讲 Day 19:Redis缓存设计模式与策略
  • 剑指offer第2版——面试题3:数组中重复的数字
  • RabbitMQ-知识技能图谱(总结篇)
  • 【时时三省】(C语言基础)建立动态链表
  • LeetCode189~191、198~214题解
  • 探秘酵母单杂交技术:解锁基因调控的密码
  • WEB虚拟主机3种部署方式全解析
  • 【Java Web 快速入门】九、事务管理
  • 【数据分享】2018-2024年中国10米分辨率春小麦和冬小麦分布栅格数据
  • Unity:GUI笔记(一)——文本、按钮、多选框和单选框、输入框和拖动条、图片绘制和框绘制
  • vue3大事件
  • 4.运算符