7.2 AQS原理

AQS 原理

概述

全称是 AbstractQueuedSynchronizer,是阻塞式锁和相关的同步器工具的框架。
特点:

  • 用 state 属性来表示资源的状态(分独占模式和共享模式),子类需要定义如何维护这个状态,控制如何获取锁和释放锁。
    • getState:获取 state 状态
    • setState:设置 state 状态
    • compareAndSetState:cas 机制设置 state 状态
    • 独占模式是只有一个线程能够访问资源,而共享模式可以允许多个线程访问资源
  • 提供了基于 FIFO 的等待队列,类似于 Monitor 的 EntryList
  • 条件变量来实现等待、唤醒机制,支持多个条件变量,类似于 Monitor 的 WaitSet

子类主要实现这样一些方法(默认抛出 UnsupportedOperationException)

  • tryAcquire
  • tryRelease
  • tryAcquireShared
  • tryReleaseShared
  • isHeldExclusively

获取锁的姿势

// 如果获取锁失败
if (!tryAcquire(arg))) {// 入队,可以选择阻塞当前线程 park unpark
}

释放锁的姿势

// 如果释放锁成功
if (tryRelease(arg)) {// 让阻塞线程恢复运行
}

实现不可重入锁

自定义同步器

/*** 自定义锁(不可重入锁)*/
class MyLock implements Lock {// 锁实现的大部分功能由该同步器类来实现// 独占锁class MySync extends AbstractQueuedSynchronizer {@Overrideprotected boolean tryAcquire(int arg) {// state = 0 表示未加锁,state = 1 表示加锁if(compareAndSetState(0, 1)) {// 加锁成功,并设置 Owner 为当前线程setExclusiveOwnerThread(Thread.currentThread());return true;}return false;}@Overrideprotected boolean tryRelease(int arg) {setExclusiveOwnerThread(null);setState(0); // state 是 volatile 变量,可以保证指令不会重排序,应当放在最后,保证其他语句对其他线程可见return true;}// 是否持有独占锁@Overrideprotected boolean isHeldExclusively() {return getState() == 1;}public Condition newCondition() {return new ConditionObject();}}private MySync sync = new MySync();// 加锁,不成功会进入等待队列@Overridepublic void lock() {sync.acquire(1);}// 加锁,可打断@Overridepublic void lockInterruptibly() throws InterruptedException {sync.acquireInterruptibly(1);}// 尝试加锁(一次)@Overridepublic boolean tryLock() {return sync.tryAcquire(1);}// 尝试加锁,带超时@Overridepublic boolean tryLock(long time, TimeUnit unit) throws InterruptedException {return sync.tryAcquireNanos(1, unit.toNanos(time));}// 解锁@Overridepublic void unlock() {sync.release(1);}// 创建条件变量@Overridepublic Condition newCondition() {return sync.newCondition();}
}

测试

@Slf4j(topic = "c.TestMyLock")
public class TestMyLock {public static void main(String[] args) {MyLock lock = new MyLock();new Thread(() -> {lock.lock();try {log.debug("locking...");Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);} finally {log.debug("unlocking...");lock.unlock();}}, "t1").start();new Thread(() -> {lock.lock();try {log.debug("locking...");} finally {log.debug("unlocking...");lock.unlock();}}, "t2").start();}
}

结果

18:13:47.514 [t1] - locking...
18:13:48.520 [t1] - unlocking...
18:13:48.520 [t2] - locking...
18:13:48.520 [t2] - unlocking...

不可重入测试
如果改为下面代码,会发现自己也会被挡住(只会打印一次 locking)

@Slf4j(topic = "c.TestMyLock")
public class TestMyLock {public static void main(String[] args) {MyLock lock = new MyLock();new Thread(() -> {lock.lock();log.debug("locking...");lock.lock();log.debug("locking...");try {log.debug("locking...");Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);} finally {log.debug("unlocking...");lock.unlock();}}, "t1").start();}
}

结果
image.png

心得

起源

早期程序员会自己通过一种同步器去实现另一种相近的同步器,例如用可重入锁去实现信号量,或反之,这显然不够优雅,于是在 JSR166(Java 规范提案)中创建了 AQS,提供了这种通用的同步器机制。

目标

AQS 要实现的功能目标

  • 阻塞版本获得 acquire 和非阻塞的版本尝试获取锁 tryAcquire
  • 获取锁超时机制
  • 通过打断取消机制
  • 独占机制及共享机制
  • 条件不满足时的等待机制

要实现的性能目标
Instead, the primary performance goal here is scalability: to predictably maintain efficiency even, or
especially, when synchronizers are contended.

设计

AQS 的基本思想其实很简单
获取锁的逻辑

while(state 状态不允许获取) {if(队列中还没有此线程) {入队并阻塞}
}
当前线程出队

释放锁的逻辑

if(state 状态允许了) {恢复阻塞的线程(s)
}

要点

  • 原子维护 state 状态
  • 阻塞及恢复线程
  • 维护队列
state 设计
  • state 使用 volatile 配合 cas 保证其修改时的原子性
  • state 使用了 32bit int 来维护同步状态,因为当时使用 long 在很多平台下测试的结果并不理想
阻塞恢复设计
  • 早期的控制线程暂停和恢复的 API 有 suspend 和 resume,但它们时不可用的,因为如果先调用的 resume,那么 suspend 将感知不到
  • 解决方法是使用 park & unpark 来实现线程的暂停和恢复,具体原理在之前讲过了,先 unpark 再 park 也没问题
  • park & unpark 是针对线程的,而不是针对同步器的,因此控制的粒度更为精细
  • park 线程还可以通过 interrupt 打断
队列设计
  • 使用了 FIFO 先入先出队列,并不支持优先级队列
  • 设计时借鉴了 CLH 队列,它是一种单向无锁队列

image.png
队列中有 head 和 tail 两个指针节点,都用 volatile 修饰配合 cas 使用,每个节点有 state 维护节点状态。
入队伪代码,只需要考虑 tail 赋值的原子性

do {// 原来的 tailNode prev = tail;// 用 cas 在原来的 tail 的基础上改为 node
} while(tail.compareAndSet(prev, node))

出队伪代码

// prev 是上一个节点
while((Node prev = node.prev).state != 唤醒状态) {
}
// 设置头结点
head = node;

CLH 好处:

  • 无锁,使用自旋
  • 快速,无阻塞

AQS 在一些方面改进了 CLH

private Node enq(final Node node) {for (;;) {Node t = tail;// 队列中还没有元素 tail 为 nullif (t == null) {// 将 head 从 null -> dummyif (compareAndSetHead(new Node()))tail = head;} else {// 将 node 的 prev 设置为原来的 tailnode.prev = t;// 将 tail 从原来的 tail 设置为 nodeif (compareAndSetTail(t, node)) {// 原来 tail 的 next 设置为 nodet.next = node;return t;}}}
}

主要用到 AQS 的工具类
image.png

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

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

相关文章

three.js官方案例(animation / skinning / ik)webgl_animation_skinning_ik.html学习记录

目录 1 WebGLCubeRenderTarget 2 TransformControls 3 CCDIKSolver 4 CCDIKHelper 4 全部脚本 1 WebGLCubeRenderTarget 球体亮 //WebGLCubeRenderTarget(size : Number, options : Object) //size - the size, in pixels. Default is 1. //options - (可选)一个保存…

软件设计之Java入门视频(15)

软件设计之Java入门视频(15) 视频教程来自B站尚硅谷: 尚硅谷Java入门视频教程,宋红康java基础视频 相关文件资料(百度网盘) 提取密码:8op3 idea 下载可以关注 软件管家 公众号 学习内容: 该视频共分为1-7…

Linux桌面溯源

X窗口系统(X Window System) Linux起源于X窗口系统(X Window System),亦即常说的X11,因其版本止于11之故。 X窗口系统(X Window System,也常称为X11或X)是一种以位图方式显示的软件窗口系统。…

zabbix 7.0 SNMP Hex数据预处理新功能

一、简介 zabbix7.0新特性是监控项新增支持SNMP Hex数据预处理。其中内置了对snmp请求结果Hex转换处理,不再需要使用繁琐的方式,如javascript脚本、替换、修整等方式处理将监控项取值做可视化处理,大福提升SNMP采集获取到Hex数据的处理效率。…

浅析 VO、DTO、DO、PO 的概念

文章目录 I 浅析 VO、DTO、DO、PO1.1 概念1.2 模型1.3 VO与DTO的区别I 浅析 VO、DTO、DO、PO 1.1 概念 VO(View Object) 视图对象,用于展示层,它的作用是把某个指定页面(或组件)的所有数据封装起来。DTO(Data Transfer Object): 数据传输对象,这个概念来源于J2EE的设…

【C++】优先级队列(底层代码解释)

一. 定义 优先级队列是一个容器适配器,他可以根据不同的需求采用不同的容器来实现这个数据结构,优先级队列采用了堆的数据结构,默认使用vector作为容器,且采用大堆的结构进行存储数据。 (1)在第一个构造函数…

Qt之元对象系统

Qt的元对象系统提供了信号和槽机制(用于对象间的通信)、运行时类型信息和动态属性系统。 元对象系统基于三个要素: 1、QObject类为那些可以利用元对象系统的对象提供了一个基类。 2、在类声明中使用Q_OBJECT宏用于启用元对象特性&#xff0c…

项目收获总结--本地缓存方案选型及使用缓存的坑

本地缓存方案选型及使用缓存的坑 一、摘要二、本地缓存三、本地缓存实现方案3.1 自己编程实现一个缓存3.2 基于 Guava Cache 实现本地缓存3.3 基于 Caffeine 实现本地缓存3.4 基于 Encache 实现本地缓存3.5 小结 四、使用缓存的坑4.1 缓存穿透4.2 缓存击穿4.3 缓存雪崩4.4 数据…

如何管理好【管理层】?

如何管理好管理层? 现在流行“找客户痛点,不如找领导G点” 管理好管理层比管理好员工更重要,不要让管理层成为传话筒。你是抱着很大期望提供优厚的待遇聘用管理层,对于所有人来说,你需要一个这样的职位,对于他需要一分工作而已。出色的管理层就像出色的员工一样非常难寻…

leetcode日记(38)字母异位词分组

最开始的想法是创建vector<vector<string>> result&#xff0c;然后遍历strs中字符串&#xff0c;遍历result中vector&#xff0c;比较vector中第一个string和strs中string&#xff0c;若为字母异位词&#xff0c;则加入vector&#xff0c;若无&#xff0c;则创建新…

新手-前端生态

文章目录 新手的前端生态一、概念的理解1、脚手架2、组件 二、基础知识1、HTML2、css3、JavaScript 三、主流框架vue3框架 四、 工具&#xff08;特定框架&#xff09;1、uinapp 五、组件库&#xff08;&#xff09;1、uView如何在哪项目中导入uView 六、应用&#xff08;各种应…

Vulnhub靶场 | DC系列 - DC2

目录 环境搭建渗透测试 环境搭建 靶机镜像下载地址&#xff1a;https://vulnhub.com/entry/dc-2,311/需要将靶机和 kali 攻击机放在同一个局域网里&#xff1b;本实验kali 的 IP 地址&#xff1a;192.168.10.146。 渗透测试 使用 nmap 扫描 192.168.10.0/24 网段存活主机 …

2024年辽宁省数学建模竞赛C题超详细解题思路+问题一案代码分享

本文将为大家带来2024年C题超详细解题思路&#xff0c;本次竞赛6000人参加&#xff0c;共计2400队伍。C题作为本次竞赛中最简单的一道题目&#xff0c;意味着选题人数也将是最多的。因此&#xff0c;本文将对C题的解题思路以及将要面对的问题&#xff0c;进行详细的说明。希望我…

基于深度学习的组织病理学图像IDC检测方法

乳腺癌可以通过对浸润性导管性乳腺癌(IDC)和浸润性小叶性乳腺癌(ILC)的内部组织区域进行检查来确诊。因此&#xff0c;早期诊断乳腺组织异常是至关重要的&#xff0c;以减少风险&#xff0c;使快速和有效的治疗。本研究旨在利用所提出的基于深度学习的算法&#xff0c;利用组织…

本地部署 EVE: Unveiling Encoder-Free Vision-Language Models

本地部署 EVE: Unveiling Encoder-Free Vision-Language Models 0. 引言1. 快速开始2. 运行 Demo 0. 引言 EVE (Encoder-free Vision-language model) 是一种创新的多模态 AI 模型&#xff0c;主要特点是去除了传统视觉语言模型中的视觉编码器。 核心创新 架构创新&#xff…

C++的deque(双端队列),priority_queue(优先级队列)

deque deque是一个容器,是双端队列,从功能上来讲,deque是一个vector和list的结合体 顺序表和链表 deque的结构和优缺点 开辟buff小数组,空间不够了,不扩容,而是开辟一个新的小数组 开辟中控数组(指针数组)指向buff小数组 将已存在的数组指针存在中控数组中间,可以使用下标访…

MICS2024|数字病理与人工智能在乳腺癌精准诊疗中的应用

小罗碎碎念 这两天在厦大开会&#xff0c;医学图像相关的学术会议。来之前一直在我自己的交流群里宣传这个会议&#xff0c;因为自己的推文与病理相关的比较多&#xff0c;所以群里的同行也比较关注这个会议病理相关的内容。 讲者简介 Scopus主页&#xff1a;https://www.scop…

旋转电连接器抗干扰性有哪几个方面?

旋转电连接器作为一种精密的电气传输装置&#xff0c;它实现了两个相对旋转部件间的功率和信号传输。通过旋转电连接器可以传输高频的交流电、高电压的交流电、大电流的交流电、弱小的直流小信号等多种电信号&#xff0c;但是由仪器之间的距离有限&#xff0c;在如此短的距离内…

蓝桥杯算法周赛开赛啦

提醒&#xff1a;19:00算法双周赛准时开启&#xff01; 单题“一血”可获得云课定制便携风扇&#xff01; &#x1f9e7;入榜最高200元&#xff01;还可抽20&#xff5e;100元现金 &#x1fad8;每月参加2次算法双周赛&#xff0c;额外发放88个实验豆&#xff01; 参赛链接…

C++:类和对象 I(访问限定符、this指针)

目录 类的定义 类的大小 访问限定符 实例化 this指针 类的定义 class就是类&#xff0c;class是C中的一个关键字 当然类也可以是C语言中的struct&#xff0c;C兼容struct&#xff0c;甚至还有一些升级 定义类的方式 class Date {}; 和C语言的struct一样&#xff0c;c…