Android---深入理解AQS和CAS原理

news2024/11/25 4:22:01

AQS

AQS 全称是 Abstract Queued Synchronizer,一般翻译为同步器。是一套实现多线程同步功能的框架,由 Doug Lea 操刀设计并开发实现的。AQS 在源码中被广泛使用,尤其是在 JUC(Java Util Concurrent)中,比如 ReentrantLock,Semaphore,CountDownLatch,ThreadPoolExecutor。

ReentrantLock 与 AQS 的关系

我们通过 ReentrantLock 与 AQS 的关系来理解 AQS 的内部的工作机制,首先从 ReentrantLock 的 lock() 方法开始。代码很简单,只是调用了 sync 的 lock() 方法。

这个 Sync 是什么呢?它是 ReentrantLock 里的一个静态内部类。ReentrantLock 并没有直接继承 AQS,而是通过内部类 Sync 来扩展 AQS 的功能。然后,ReentrantLock 中存有 Sync 的全局引用

Sync 在 ReentrantLock 中有两种实现:NonfairSyncfairSync,分别对应非公平锁公平锁。非公平锁实现源码如下:

在非公平锁的 lock() 方法中,如果通过 CAS 设置变量 state 成功,表示当前线程获取锁成功,则将当前线程设置为独占线程。如果通过 CAS 设置变量 state 失败,表示当前锁正在被其它线程持有,则进入 acquire() 方法进行后续处理。acquire() 方法定义在 AQS 中,具体如下

acquire() 是一个比较重要的方法,可以将其拆解为 3 个主要步骤:

1. tryAcquire() 方法主要目的是尝试获取锁;

2. addWaiter() 如果 tryAcquire() 尝试获取锁失败则调用 addWaiter() 将当前线程添加到一个等待队列中;

3. acquireQueued() 处理加入到队列中的节点,通过自旋去尝试获取锁,根据情况将线程挂起或者取消。

以上3种方法都被定义在 AQS 中,但 tryAcquire() 有点特殊,其实现如下

默认情况下直接抛出异常,因此它需要在子类中复写。也就是说真正获取锁的逻辑由子类同步器自己实现

RenntrantLock 中 tryAcquire() 方法以非公平锁为例如下

获取当前线程,判断当前锁的状态,如果 state = 0,表示当前是无锁的状态。通过 CAS 更新 state 的值,返回 true。如果是同一个线程重入,则直接增加重入次数。上述情况都不满足,则获取锁失败,返回 false。

下面用一张图表示 ReentrantLock 的 lock() 方法的过程

从图中可以看出,在 ReentratLock 执行 lock() 的方法中,大部分同步机制的核心逻辑都已经在 AQS 中实现。ReentrantLock 只要自身实现某些特定步骤下的方法即可。这种设计模式叫做模板模式。在 Android 中,这种模式很常见。比如 Activity 的声明周期都已经在 framwork 中定义好了。子类 Activity 只要在相应的 onCreate()、onPause() 等声明周期方法中提供相应的实现即可。

JUC 包中其他组件,例如 CountDownLatch、Semaphor 等。都是通过一个内部类 Sync 来继承 AQS,然后在内部中通过操作 Sync 来实现同步。好处是将线程的控制逻辑控制在 Sync 内部,而对外面向用户提供的接口是自定义锁


AQS 核心功能原理分析

AQS 中几个关键属性,Node state,如下图所示:

state 表示当前锁状态

state = 0 时表示无锁状态;

state > 0 时,表示已经有线程获得了锁,也就是 state = 1。如果同一个线程多次获得同步锁的时候,state 会递增,比如重入 5 次,那么 state = 5。而在释放锁的时候,同样需要释放 5 次直到 state = 0,其他线程才有资格获得锁。

state的一个功能是实现锁的独占模式或者共享模式

\bullet 独占模式: 只有一个线程能够持有同步锁。比如在独占模式下,可以把state的初始值设置成0,当某个线程申请锁对象时,需要判断state的值是不是0,如果不是0的话意味着其他线程已经持有该锁,则本线程需要阻塞等待。

\bullet 共享模式: 可以有多个线程持有同步锁。比如某项操作允许10个线程同时进行,超过这个数量的线程就需要阻塞等待,那么只需要在线程申请对象时判断state的值是否小于10。如果<10,就将 state 加 1 后继续同步语句的执行;如果等于 10,说明已经有 10个线程在同时执行该操作,本线程需要阻塞等待。

Node 双端队列节点

Node 是一个先进先出的双端队列,并且是等待队列。当多线程争用资源被阻塞时会进入此队列。这个队列是 AQS 实现多线程同步的核心,在 AQS 中有两个 Node 指针,分别指向队列的 headtail。Node 的主要结构如下

默认情况下,AQS 中的链表结构如下图所示

获取锁失败后续流程分析

锁的意义是使竞争到锁对象的线程执行同步代码块。多个线程竞争锁时,竞争失败的线程需要被阻塞等待后续唤醒。那么,ReentrantLock 是如何实现让线程等待并唤醒的呢?

在 ReentrantLock.lock() 阶段,在 acquire() 方法中会先后调用 tryAcquire、addWaiter、acquireQueued 来处理。tryAcquire 在 ReentrantLock 中被复写并实现。如果返回 true 说明成功获取锁,就继续执行同步代码语句。

如果 tryAcquire 返回 false,那么当前线程会被 AQS 如何处理呢?

1. 通过 addWaiter()方法把线程加入到 node 等待队列中。

首先,当前获取锁失败的线程会被添加到一个等待队列的末端,具体源码如下

有两种情况会使插入队列失败:1)tail 为空,说明队列从未初始化。因此需要调用 enq 方法在队列中插入一个空的 Node;2)CompareAndSetTail 失败,说明插入过程中有线程修改了此队列。因此,需要调用 enq() 将当前 Node 重新插入到队列末端。经过 addWaiter() 方法之后,此时线程以 Node 的方式被加入到队列的末端。但是线程还没有执行阻塞操作,真正的阻塞操作是在 acquireQueued() 方法中

2. 在 acquireQueued 方法中把线程阻塞

在 acquireQueued 方法中并不会立即挂起该节点中的线程,在插入节点的过程中,之前持有锁的线程可能已经执行完毕并释放锁,这里使用自旋再次去尝试获取锁。如果自旋后还是没有获取到锁,则将该线程挂起或阻塞。acquireQueued() 的源码如下

可以看出在,shouldParkAfterFailedAcquire() 方法中会判断该线程是否应该被阻塞。其代码如下

首先,获取前驱节点的 waitStates 值。Node 中的 waitStates 值一共有5种取值,分别代表的意义如下。

接下来,根据 waitStates 的值进行不同的操作,主要有以下几种情况

\bullet 如果 waitStatus 等于 SIGNAL,返回 true 将当前线程挂起,等待后续唤醒操作即可;

\bullet 如果 waitStatus 大于 0 也就是 CANCLE 状态,会将此前驱节点从队列中删除,并在循环中逐步寻找下一个不是 “CANCEL”状态的节点作为当前节点的前驱节点。

\bullet 如果 waitStatus 既不是 SINGAL 也不是 CANCEL,则将当前节点的前驱节点状态设置为 SIGNAL。好处是下一次执行 shouldParkAfterFailedAcquire 时可以直接返回 true,挂起线程。

如果 shouldParkAfterFailedAcquire 返回 true 表示线程需要被挂起,那么会继续调用 parkAndCheckInterrupt 方法执行真正的阻塞线程代码

这个方式只调用了 LockSupport 中的 park() 方法。在LockSupport.park() 方法中调用了 Unsafe API 来执行底层 Native 方法,将线程挂起。

获取锁的大体流程如下:

1. AQS 的模板方法 acquire 通过调用子类自定义实现的 tryAcquire 获取锁;

2. 如果获取锁失败,通过 addWaiter 方法将线程构造成 Node 节点插入到同步队列队尾;

3. 在 acquireQueued 方法中以自旋的方式尝试获取锁。如果失败则判断是否需要将当前线程阻塞;如果需要阻塞则最终执行 LockSupport(Unsafe)中的 native API 来实现线程阻塞。

AQS 是如何尝试唤醒等待队列中被阻塞的线程呢?

上面加锁阶段,被阻塞的线程需要重新唤醒才能继续执行。

同加锁过程一样,释放锁需要从 ReentrantLock 的 unlock() 开始。具体代码如下

可以看出,首先调用 tryRelease 方法尝试释放锁,如果成功,最终会调用 AQS 的 unparkSuccessor() 来实现释放锁的操作。unparkSuccessor 具体实现如下

CAS

不管在加锁还是释放锁的阶段,都会用到一种通用的操作:compareAndSetXXX,这种操作最终会调用 Unsafe 中的 API 进行 CAS 操作

CAS 全称是 Compare And Swap,译为比较和替换,是一种通过硬件实现并发安全的常用技术。底层通过利用 CPU 的 CAS 指令对缓存加锁或总线加锁的方式来实现多处理器之间的原子操作。实现过程主要有3个操作数:1)内存值V;2)旧的预期值E;3)要修改的新值U

当且仅当预期值 E 和内存值 V 相同时,才将内存值 V 修改为 U,否则什么都不做。CAS 底层会根据操作系统和处理器的不同来选择对应的调用代码。以 Windows 和 X86 处理器为例

\bullet 如果是多处理器。通过带 lock 前缀的 cmpxchg 指令对缓存加锁或总线程加锁的方式来实现多处理器之间的原子操作

\bullet 如果是单处理器。通过 cmpxchg 指令完成原子操作。

总结

AQS是一套框架,在框架内部已经封装好了大部分同步需要的逻辑。在AQS内部维护了一个状态指示器state和一个等待队列Node。AQS有两种不同的实现:

● 独占锁(ReentrantLock等)

● 分享锁(CountDownLatch、 读写锁等)

本次主要从独占锁的角度深入分析了AQS的加锁和释放锁的流程。

理解 AQS 的原理对理解 JUC 包中其他组件实现的基础有帮助。可能需要子类同步器实现的方法如下:

\bullet lock()

\bullet tryAcquire(int):独占方式。尝试获取资源,成功则返回 true,失败则返回 false;

\bullet tryRelease(int):读占方式。尝试释放资源,成功 则返回 true,失败则返回 false;

\bullet tryAcquireShared(int):共享方式。尝试获取资源,负数表示失败;0表示成功,但没有剩余可用资源;整数表示成功,且有剩余资源。

\bullet tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待节点返回 true,否则返回 faltata

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1090745.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

allure测试报告生成逻辑--解决在Jenkins里打开allure报告页面后空白显示无数据问题(以window环境为例)

前言 相信大家在用Jenkins持续集成+ant自动构建+jmeter接口测试+pytest代码.xml文件转化+allure测试报告为一体的接口自动化测试构建过程中,都会遇到Jenkins里打开allure报告页面后空白显示无数据问题这一现象级问题,今天Darren洋就给大家分享一下如何讲讲allure测试报告生成…

MySQL——源码安装教程

MySQL 一、MySQL的安装1、RPM2、二进制3、源码 二、源码安装方式三、安装过程1、上传源码包2、解压当前文件并安装更新依赖3、对MySQL进行编译安装 四、其他步骤 一、MySQL的安装 首先这里我来介绍下MySQL的几种安装方式&#xff1a; 一共三种&#xff0c;RPM安装包、二进制包…

通过okhttp调用SSE流式接口,并将消息返回给客户端

通过一个完整的java示例来演示如何通过okhttp来调用远程的sse流式接口 背景&#xff1a;我们有一个智能AI的聊天界面&#xff0c;需要调用三方厂商的大模型chat接口&#xff0c;返回答案&#xff08;因为AI去理解并检索你的问题的时候这个是比较耗时的&#xff0c;这个时候客户…

10-SRCNN-使用CNN实现超分辨成像

文章目录 utils_dataset.pymodel.pytrain.pyuse.py主要文件 utils_dataset.py 工具文件,主要用来制作dataset,便于加入dataloader,用于实现数据集的加载和并行读取 model.py 主要写入网络(模型) train.py 主要用于训练 use.py 加载训练好的模型,用于测试或使用 utils_dat…

Spring实战 | Spring AOP核心秘笈之葵花宝典

Spring实战系列文章&#xff1a; Spring实战 | Spring IOC不能说的秘密&#xff1f; 国庆中秋特辑系列文章&#xff1a; 国庆中秋特辑&#xff08;八&#xff09;Spring Boot项目如何使用JPA 国庆中秋特辑&#xff08;七&#xff09;Java软件工程师常见20道编程面试题 国庆…

IDEA的常用设置

【1】进入设置&#xff1a; 【2】设置主题&#xff1a; 【3】编辑区的字体变大或者变小&#xff1a; 【4】鼠标悬浮在代码上有提示&#xff1a; 【5】自动导包和优化多余的包&#xff1a; 手动导包&#xff1a;快捷键&#xff1a;altenter 自动导包和优化多余的包&#xf…

计算机网络第2章-HTTP和Web协议(2)

Web和HTTP 一个新型应用即万维网&#xff08;World Wide Web&#xff09;Web。 HTTP概况 Web的应用层协议是超文本传输协议&#xff08;HTPP&#xff09;&#xff0c;它是Web的核心。 HTTP由两个程序实现&#xff1a;一个用户程序和一个服务器程序。 Web页面&#xff08;W…

leetcode-518. 零钱兑换 II

1. 题目 链接: 零钱兑换II 2. 解决方案1 #include <stdio.h> #include <stdlib.h>int change(int amount, int* coins, int coinsSize){int dp[amount1];//确定dp大小memset(dp, 0, sizeof(int) * (amount1));dp[0] 1;//初始化为0for(int i 0 ; i < coins…

LED电子屏幕可以通过什么方式进行人屏互动

传统的LED大屏幕以单向传播的形式面向观众&#xff0c;不仅被动&#xff0c;而且逐渐缺乏动感和创新。随着LED显示技术的蓬勃发展&#xff0c;现在观众与LED电子大屏幕的方式越来越多。那么现阶段实现LED显示屏人屏互动的主要方式都有哪些呢&#xff1f;带你8分钟了解LED互动地…

KASan介绍

目录 概括介绍 配置说明 单独关闭读或写检查 操作使用 影响及注意事项 结果解读 使用注意 实现原理简介 KASAN原理 malloc原理 内容参考 概括介绍 KernelAddressSANitizer &#xff08;KASAN&#xff09; 是一个动态内存错误检测器。它提供了一个快速而全面的解决方…

D课堂 | 如何设置域名解析?解析记录类型选哪个?

上回&#xff0c;D妹和各位小伙伴们介绍了DNS的作用和原理——《什么是DNS&#xff1f;DNS是怎么运作的&#xff1f;》&#xff0c;相信大家对DNS已经有了一定的认识。 DNS是互联网不可或缺的基础服务&#xff0c;核心作用是将域名翻译成计算机可读取的IP地址&#xff0c;也就是…

VMware搭载linux出现的bugs

---------后续在实际Linux项目复盘过程中有遇到问题(解决办法)会不定时更新.......----------- ques: Linux自带的media目录用于挂载或可移动存储设备已满&#xff08;造成这一原因是由于我多次创建新的虚拟机并在同一虚拟目录下挂载同一镜象导致有些残存文件没有删除干净&…

【OpenCv光流法进行运动目标检测】

opencv系列文章目录 文章目录 opencv系列文章目录前言一、光流法是什么&#xff1f;二、光流法实例1.C的2.C版本3.python版本 总结 前言 随着计算机视觉技术的迅猛发展&#xff0c;运动目标检测在图像处理领域中扮演着至关重要的角色。在现实世界中&#xff0c;我们常常需要追…

JDK21要来了,协程对Java带来什么

目录 前言 协程是什么 多线程有什么问题&#xff1f; 协程的线程模型 Reactor模型 使用协程后 RPC并发 IO阻塞 网络IO 磁盘IO epoll为什么不支持磁盘io&#xff1f; Kotlin与Go的协程 Go 使用 Go的协程调度(GPM模型) Kotlin 使用 Kotlin协程调度 阿里Wisp协程…

Linux程序调试工具使用整理

Linux程序调试工具使用整理 GDB调试入门 GDB是GNU开源组织发布的一个强大的UNIX下的程序调试工具。或许&#xff0c;各位比较喜欢那种图形界面方式的&#xff0c;像VC、BCB等IDE的调试&#xff0c;但如果你是在 UNIX平台下做软件&#xff0c;你会发现GDB这个调试工具有比VC、…

万界星空科技可视化数字大屏应用场景及作用

一、MES系统大屏显示&#xff1a;实时监控生产数据的关键 随着制造业的发展&#xff0c;现代企业越来越依赖于高效的生产管理系统来保证生产效率和质量。其中&#xff0c;MES系统数据大屏显示成为了监控生产数据的关键工具。通过实时监控和显示生产数据&#xff0c;企业能够及…

智能网关在校园能耗监测系统中的应用介绍

安科瑞 崔丽洁 摘要&#xff1a;国家提出了全社会节能减排的战略举措&#xff0c;节约型校园的建设是实现这一举措的重要内容。为了对校园能耗实行量化管理、实时监测&#xff0c;需要建立一个完善的监管体系校园节能监管体系。而节能监管体系的核心是能耗监测平台&#xff0c;…

解决react集成typescript报错:找不到名称“div“之类的错误

现象&#xff1a; 原因&#xff1a;Typescript 不希望在 Typescript 文件中看到 JSX元素。 解决此问题的最简单方法是将文件后缀从 .ts 重命名为 .tsx 。

【学习笔记】DTM分布式事务

分布式事务是什么 本文的分布式事务指的是DTM下的分布式事务。 分布式事务有两类&#xff0c;这里指的是跨数据库、跨服务的分布式事务。 分布式事务指事务的发起者、资源及资源管理器和事务协调者分别位于分布式系统的不同节点之上。 CAP理论 C&#xff08;一致性&#x…

【UVM 验证平台打印时间单位控制】

UVM 验证平台打印时间单位控制 UVM 具有丰富的打印功能&#xff0c;打印信息会包含时间/打印位置等信息&#xff0c;根据打印时间可以方便的在波形上找到错误点。默认打印时间单位时fs&#xff0c;由于单位太小会导致打印信息上的时间信息比较长&#xff0c;不方便查看与查找。…