JUC-AQS

news/2025/2/14 0:02:42/

如何利用CAS实现一个同步框架

  • state:共享标记位。利用CAS修改,达到同步管理
  • 等待队列:存储需要等待获取锁的线程

共享标记位state=0 表示资源是空闲的;state=1表示有1个线程获取到资源,如何独占模式,判断持有锁的线程是否是当前线程,若是,则state变为2,达到可重入性

 如果获取锁失败立即返回,则不需要入队

如果需要不断的尝试,业务侧可循环适用用tryLock不断重试

AQS作用

定义了加锁、释放锁的骨架

AbstractQueuesSynchorizer属性解释

Queues:队列

Synchorizer: 同步阻塞

volatile int state

        用于记、判断共享资源正在被占用的情况

为什么适用int而不是boolean?

为了可重入(reentrantLock多次加锁,state需要一致递增,最后需要对称释放锁)

为了共享锁模式

volatile Node head、tail

        头尾节点:线程没有获取到锁,需要排队

Node 的属性

  • Node prev、next  双向链表
  • Thread thread    当前入队等待的线程
  • Node nextWaiter     
  • int waitStatus  等待状态   1, -1, -2, -3

  核心方法

以下方法是骨架,实际的实现由子类完成

tryAcquire(int arg)

tryRelease(int arg)

acquire(int arg)

      获取锁

    public final void acquire(int arg) {if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();}

  获取锁失败,则进入等待队列

    private Node addWaiter(Node mode) {Node node = new Node(Thread.currentThread(), mode);// Try the fast path of enq; backup to full enq on failureNode pred = tail;if (pred != null) {node.prev = pred;// 尝试快速入队if (compareAndSetTail(pred, node)) {pred.next = node;return node;}}// 完整的入队操作enq(node);return node;}

if (compareAndSetTail(pred, node)) {
    pred.next = node;
    return node;
}

问题: compareAndSetTail 是原子的,但是整个if语句不是原子的,为什么不会出问题?

compareAndSetTail是通过原子的方式设置尾节点(此时包含了tail=node:将当前的node节点设置尾tail),pred.next = node;只是设置之前的tail节点的next节点,这个操作不会有其他线程干扰

问题:    为什么要使用 Node pred = tail?

因为tail节点的指针会被其他线程修改,类似写时复制,如果tail节点改变,pred的指针还是指向原来的tail节点

问题:   为什么要先尝试快速入队,而不是直接入队

应该是测试的结果,认为tryAcquire失败,大概率尾节点不为空,先尝试入队,如果成功,则少了一步完整队逻辑中的 tail为null的判断

完整的入队(循环cas直到入队成功)

    private Node enq(final Node node) {for (;;) {Node t = tail;if (t == null) { // Must initializeif (compareAndSetHead(new Node()))tail = head;} else {node.prev = t;if (compareAndSetTail(t, node)) {t.next = node;return t;}}}}

对线程进程挂起与响应(推进队列中的线程进行消费)   

    final boolean acquireQueued(final Node node, int arg) {boolean failed = true;try {boolean interrupted = false;for (;;) {// 获取当前节点的上一个节点final Node p = node.predecessor();// 上一个节点是head节点 && 尝试获取锁成功// head节点是一个虚节点,当前节点获取锁后,变成了head节点if (p == head && tryAcquire(arg)) {setHead(node);p.next = null; // help GCfailed = false;return interrupted;}// 判断是否需要挂起,避免过度自旋,消耗CPUif (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())interrupted = true;}} finally {if (failed)cancelAcquire(node);}}

 acquire完整的流程

  • 先tryAcquire获取锁,获取锁失败,则进入等待队列
  • 先尝试“快速入队”,失败后在使用“完整入队”。(完整入队比快速入队多判断了一个tail节点是否为null,因为tryAcquire失败,大概率tail不为null,使用快速入队能提高性能)
  • 自旋获取锁(判断当前节点的前置节点是否是head节点,并且尝试获取锁,获取失败一直重试)
  • 线程挂起(重试一定次数,线程会挂起,避免无效的消耗CPU)

release(int arg)

    public final boolean release(int arg) {// 尝试释放锁if (tryRelease(arg)) {Node h = head;// 唤醒等待的线程if (h != null && h.waitStatus != 0)unparkSuccessor(h);return true;}return false;}

唤醒线程unpark

    private void unparkSuccessor(Node node) {int ws = node.waitStatus;if (ws < 0)compareAndSetWaitStatus(node, ws, 0);Node s = node.next;if (s == null || s.waitStatus > 0) {s = null;// 从后往前找第一个 waitStatus <=0的for (Node t = tail; t != null && t != node; t = t.prev)if (t.waitStatus <= 0)s = t;}if (s != null)LockSupport.unpark(s.thread);}

加锁、入队、出队、挂起、唤醒

问题:为什么从后往前找?

enq入队方法,从tail尾入队,compareAndSetTail将当前节点设置为tail后,FIFO的倒数第二个节点 ->tail还没有创建链接(t.next = node),如果从前往后找,可能会漏掉此节点。这也是为什么 node.prev = t 在 compareAndSetTail 之前的原因

node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}


http://www.ppmy.cn/news/1255566.html

相关文章

Unity Canvas、Canvas Scaler、Graphic Raycaster、EventSystem 组件详解

文章目录 0. 参考文章1. Canvas1.1 Screen Space-Overlay —— 屏幕空间覆盖模式1.2 Screen Space-Camera —— 相机模式1.3 World Space —— 世界模式 2. Canvas Scaler&#xff1a;控制UI画布的放大缩放的比例2.1 Constant Pixer Size —— 恒定像素2.2 Scale With Screen S…

domain参数错误导致讯飞星火大模型:发生错误,错误码为:10404

问题 开通讯飞星火大模型api调用后&#xff0c;使用官方demo调用报错10404&#xff0c;最终发现是domain参数需要跟调用的版本保持一致&#xff0c;1.5&#xff0c;2&#xff0c;3版本分别传general,generalv2,generalv3&#xff0c;传错了还报错10404&#xff0c;感觉真没这必…

Python 文件错误 SyntaxError: Non-Ascii Character \xe2

此错误表明您正在代码中写入非 ASCII 字符。 在编译时&#xff0c;解释器感到困惑并抛出 SyntaxError: Non-ASCII character ‘\xe2’ 。 ASCII 字符使用与 UTF-8 的前 128 个字符相同的编码&#xff0c;因此 ASCII 文本与 UTF-8 兼容。 首先&#xff0c;您必须了解 ASCII 字符…

【网页会话技术jwt在springboot实现】

文章目录 网页会话技术 JWT 在 Spring Boot 实现什么是JWT&#xff1f;Spring Boot中实现JWT1. 引入依赖2. 创建JWT工具类3. 使用JWT进行认证4. 验证JWT 网页会话技术 JWT 在 Spring Boot 实现 什么是JWT&#xff1f; JWT是一种紧凑且自包含的方式&#xff0c;用于在各方之间…

C++中用于动态内存的new和delete操作符

文章目录 1、动态分配内存的应用2、动态分配内存与分配给普通变量的内存有什么不同?3、C 中如何分配/释放内存4、new 操作符4.1 使用new的语法4.2 初始化内存4.3 分配内存块4.4 普通数组声明 Vs 使用new4.5 如果运行时没有足够内存可用怎么办&#xff1f; 5、delete 操作符 C/…

【hacker送书活动第7期】Python网络爬虫入门到实战

第7期图书推荐 内容简介作者简介大咖推荐图书目录概述参与方式 内容简介 本书介绍了Python3网络爬虫的常见技术。首先介绍了网页的基础知识&#xff0c;然后介绍了urllib、Requests请求库以及XPath、Beautiful Soup等解析库&#xff0c;接着介绍了selenium对动态网站的爬取和S…

【CTA认证】Android8实现android6以下的应用运行时也要申请权限

需求 CTA入网认证&#xff0c;要求低版本比如Android6以下的应用&#xff0c;运行时&#xff0c;也需要有运行时权限(Runtime Permission)功能&#xff0c;不能默认就取到权限&#xff0c;必须人工在设置中打开才可。 环境 Android 8 实现 frameworks 修改思路是所有APP都…

拼多多财报解读:连接高质量供给与全球消费者,多多跨境动力澎湃

全面拥抱“高质量发展”新阶段后&#xff0c;拼多多再次交出一份惊人的成绩单。 11月28日&#xff0c;拼多多集团发布截至9月30日的2023年第三季度业绩报告。财报显示&#xff0c;拼多多集团今年第三季度收入为688.4亿元&#xff0c;同比增长93.9%&#xff1b;实现美国通用会计…