Java并发-06-AQS(AbstractQueuedSynchronizer)相关

1-概述

       AQS全称是 AbstractQueuedSynchronizer,是阻塞式锁和相关的同步器工具的框架。同步器的设计是基于模板方法模式的,也就是说,使用者需要继承同步器并重写指定的方法,随后将同步器组合在自定义同步组件的实现中,并调用同步器提供的模板方法,而这些模板方法将会调用使用者重写的方法。

1.1-主要特点

(1)用 state 属性来表示资源的状态(分独占模式和共享模式),子类需要定义如何维护这个状态,控制如何获取锁和释放锁。其中state的具体定义由子类(开发者)去设计
getState - 获取 state 状态;
setState - 设置 state 状态;
compareAndSetState - cas 机制设置 state 状态;
独占模式是只有一个线程能够访问资源,而共享模式可以允许多个线程访问资源。

(2)提供了基于 FIFO 的等待队列,类似于 Monitor 的 EntryList
(3)条件变量来实现等待、唤醒机制,支持多个条件变量,类似于 Monitor 的 WaitSet。

1.2-AQS中重要的方法描述

同步器可重写的方法
方法名称描述
protected boolean tryAcquire(int arg)独占式获取同步状态,实现该方法需要查询当前状态并判断同步状态是否符合预期,然后再进行 CAS 设置同步状态
protected boolean tryRelease(int arg)独占式释放同步状态,等待获取同步状态的线程将有机会获取同步状态
protected int tryAcquireShared(int arg)共享式获取同步状态,返回大于等于0的值,表示获取成功,反之,获取失败
protected boolean tryReleaseShared(int arg)共享式释放同步状态
protected boolean isHeldExclusively()当前同步器是否在独占模式下被线程占用,一般该方法表示是否被当前线程所独占


 


 


 

实现自定义同步组件时,将会调用同步器提供的模板方法,这些(部分)模板方法与描述如下:

方法名称描述
void acquire(int arg)独占式获取同步状态,如果当前线程获取同步状态成功,则由该方法返回,否则,将会进入同步队列等待,该方法将会调用重写的 tryAcquire(intarg)法
void acquireInterruptibly(int arg)与acquire(int arg)相同,但是该方法响应中断,当前线程未获取到同步状态而进入同步队列中,如果当前线程被中断,则该方法会抛出InterruptedException 并返回
boolean tryAcquireNanos(int arglong nanos)在acquireInterruptibly(int arg)基础上增加了超时限制,如果当前线程在超时时间内没有获取到同步状态,那么将会返回 false,如果获取到了返回 true
void acquireShared(int arg)共享式的获取同步状态,如果当前线程未获取到同步状态,将会进入同步队列等待,与独占式获取的主要区别是在同一时刻可以有多个线程获取到同步状态
void acquireSharedInterruptibly(int arg)与acquireShared(int arg)相同,该方法响应中断
boolean tryAcquireSharedNanos(intarg, long nanos)在acquireSharedInterruptibly(intarg)基础上增加了超时限制
boolean release(int arg)独占式的释放同步状态该方法会在释放同步状态之后,将同步队列中第个节点包含的线程唤醒
boolean releaseShared(int arg)共享式的释放同步状态
Collection<Thread> getQueuedThreads)获取等待在同步队列上的线程集合

       同步器提供的模板方法基本上分为3 类:独占式获取与释放同步状态、共享式获取与释放同步状态和查询同步队列中的等待线程情况。自定义同步组件将使用同步器提供的模板方法来实现自己的同步语义。

1.3-设计思想

state设计:state 使用 volatile 配合 cas 保证其修改时的原子性;
阻塞恢复设计:park & unpark 来实现线程的暂停和恢复;
队列设计:使用了 FIFO 先入先出队列,并不支持优先级队列;借鉴了 CLH 队列,它是一种单向无锁队列。

2-基于AQS实现自定义锁

2.1-自定义实现不可重入排他锁

基于AQS可以快速实现自定义锁,下面就来实现一个 排他锁,不可重入。

public class MyLock1 implements Lock {static MySync1 mySync1=new MySync1();//尝试 加锁,不成功就进入等待队列@Overridepublic void lock() {mySync1.acquire(1);}//尝试,不成功,进入等待队列,可打断@Overridepublic void lockInterruptibly() throws InterruptedException {mySync1.acquireInterruptibly(1);}//尝试一次,不成功返回,不进入队列@Overridepublic boolean tryLock() {return mySync1.tryAcquire(1);}//尝试,不成功,进入等待队列,有时限@Overridepublic boolean tryLock(long time, TimeUnit unit) throws InterruptedException {return mySync1.tryAcquireNanos(1,unit.toNanos(time));}@Overridepublic void unlock() {mySync1.release(1);}@Overridepublic Condition newCondition() {return mySync1.newCondition();}//独占锁实现tryAcquire,tryRelease,isHeldExclusivelystatic class MySync1 extends AbstractQueuedSynchronizer{@Overrideprotected boolean tryAcquire(int acquire) {//我们可以设计state 为0 时表示 没有线程占用锁if(acquire==1){if(compareAndSetState(0,1)){setExclusiveOwnerThread(Thread.currentThread());//state字段是volatile 可以防止指令重排序 所以将线程设置 代码放置 在setState之前setState(1);return true;}}return false;}@Overrideprotected boolean tryRelease(int acquire) {//能够进入这个方法表示当前线程肯定已经获取到锁了if(acquire==1){if(getState()==0){throw new IllegalMonitorStateException();}setExclusiveOwnerThread(null);setState(0);return true;}return false;}protected Condition newCondition() {return new ConditionObject();}@Overrideprotected boolean isHeldExclusively() {return getState()==1;}}
}


测试排他性:

 MyLock1 myLock1=new MyLock1();new Thread(()->{myLock1.lock();try {log.info("t1-lock");TimeUnit.SECONDS.sleep(1);}catch (Exception e){log.error("t1-error");}finally {myLock1.unlock();log.info("t1-unlock");}},"t1").start();new Thread(()->{myLock1.lock();try {log.info("t2-lock");}catch (Exception e){log.error("t2-error");}finally {myLock1.unlock();log.info("t2-unlock");}},"t2").start();

控制台输出:

15:36:17.590 [t1] INFO  com.ycmy2023.aqs.demo01.TestDemo - t1-lock
15:36:18.595 [t2] INFO  com.ycmy2023.aqs.demo01.TestDemo - t2-lock
15:36:18.595 [t2] INFO  com.ycmy2023.aqs.demo01.TestDemo - t2-unlock
15:36:18.595 [t1] INFO  com.ycmy2023.aqs.demo01.TestDemo - t1-unlock

由此可见:必须当线程1持有锁释放的时候,线程2才能获取到锁

测试不可重入:

 MyLock1 myLock1=new MyLock1();new Thread(()->{myLock1.lock();log.info("t1-获取锁1");myLock1.lock();log.info("t1-获取锁2");try {log.info("t1-lock");TimeUnit.SECONDS.sleep(1);}catch (Exception e){log.error("t1-error");}finally {myLock1.unlock();log.info("t1-unlock");}},"t1").start();

控制台输出:

15:38:42.028 [t1] INFO  com.ycmy2023.aqs.demo01.TestDemo - t1-获取锁1

一直阻塞在第一个日志输出的地方,不会输出第二个日志,说明同一个线程获取到锁,不能再次加锁。

2.2-自定义实现共享锁

      设计一个同步工具:该工具在同一时刻,只允许至多两个线程同时访问,超过两个线程的访问将被阻塞。

分析需求:

(1)确定访问模式。能够在同一时刻支持多个线程的访问,这显然是共享式访问,因此,需要使用同步器提供的acquireShared(int args)方法等和Shared 相关的方法这就要求必须重写 tryAcquireShared(int args)方法和 tryReleaseShared(int args)方法,这样才能保证同步器的共享式同步状态的获取与释放方法得以执行。

(2)定义资源数。在同一时刻允许至多两个线程的同时访问,表明同步资源数为2,这样可以设置初始状态 status 为2,当一个线进行获取,state 减1,该线程释放,则 state加1,状态的合法范围为 0、1和2。其中0表示当前已经有两个线获取了同步资源,此时再有其他线程对同步状态进行获取,该线程只能被阻塞。在同步状态变更时需要使用compareAndSet(int expect,int update)方法做原子性保障。

public class MySharedLock implements Lock {private static MySync2  sync=new MySync2(2);@Overridepublic void lock() {sync.acquireShared(1);}@Overridepublic void lockInterruptibly() throws InterruptedException {}@Overridepublic boolean tryLock() {return false;}@Overridepublic boolean tryLock(long time, TimeUnit unit) throws InterruptedException {return false;}@Overridepublic void unlock() {sync.releaseShared(1);}@Overridepublic Condition newCondition() {return null;}private static class MySync2 extends AbstractQueuedSynchronizer{MySync2(int count){setState(count);}@Overrideprotected int tryAcquireShared(int reduceCount) {for(;;){int current=getState();int newCount=current-reduceCount;if(newCount <0 || compareAndSetState(current,newCount)){return newCount;}}}@Overrideprotected boolean tryReleaseShared(int returnCount) {for(;;){int current=getState();int newCount=current+returnCount;if(compareAndSetState(current,newCount)){return true;}}}}
}

测试开发写的锁

  MySharedLock lock=new MySharedLock();new Thread(()->{lock.lock();log.info("t1-获取锁1");try {log.info("t1-lock");TimeUnit.SECONDS.sleep(5);}catch (Exception e){log.error("t1-error");}finally {lock.unlock();log.info("t1-unlock");}},"t1").start();new Thread(()->{lock.lock();log.info("t2-获取锁1");try {log.info("t2-lock");TimeUnit.SECONDS.sleep(8);}catch (Exception e){log.error("t2-error");}finally {lock.unlock();log.info("t2-unlock");}},"t2").start();new Thread(()->{lock.lock();log.info("t3-获取锁1");try {log.info("t3-lock");TimeUnit.SECONDS.sleep(4);}catch (Exception e){log.error("t3-error");}finally {lock.unlock();log.info("t3-unlock");}},"t3").start();

控制台输出:

16:02:50.705 [t2] INFO  com.ycmy2023.aqs.demo02.TestDemo02 - t2-获取锁1
16:02:50.705 [t1] INFO  com.ycmy2023.aqs.demo02.TestDemo02 - t1-获取锁1
16:02:50.708 [t2] INFO  com.ycmy2023.aqs.demo02.TestDemo02 - t2-lock
16:02:50.708 [t1] INFO  com.ycmy2023.aqs.demo02.TestDemo02 - t1-lock
16:02:55.709 [t1] INFO  com.ycmy2023.aqs.demo02.TestDemo02 - t1-unlock
16:02:55.709 [t3] INFO  com.ycmy2023.aqs.demo02.TestDemo02 - t3-获取锁1
16:02:55.709 [t3] INFO  com.ycmy2023.aqs.demo02.TestDemo02 - t3-lock
16:02:58.717 [t2] INFO  com.ycmy2023.aqs.demo02.TestDemo02 - t2-unlock
16:02:59.716 [t3] INFO  com.ycmy2023.aqs.demo02.TestDemo02 - t3-unlock

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

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

相关文章

使用按钮从 SAP 系统内打开 Excel 文件

了解如何通过 SAP 屏幕上创建的按钮打开所需的 Excel 文件。为了演示这一点&#xff0c;将指导您完成以下步骤。 使用 del 命令删除 SAP 上不必要的元素添加一个按钮&#xff0c;单击后打开弹出窗口创建一个函数来选择 excel 文件创建打开所需 excel 文件的函数 定制 登录 S…

Python 框架学习 Django篇 (三) 链接数据库

只要你是做后端开发的&#xff0c;那么就离不开各种数据库&#xff0c;Django框架对各种数据库都非常友好&#xff0c;比如常见的PostgreSQL、MySQL、SQLite、Oracle&#xff0c;django都对他们提供了统一调用api&#xff0c;我们这里主要使用mysql数据库作为演示 一、ORM机制 …

10月20日星期五今日早报简报微语报早读

10月20日星期五&#xff0c;农历九月初六&#xff0c;早报微语早读分享。 1、上海4岁走失女童遗体在宁波市某滩涂被发现&#xff0c;排除刑事案件&#xff1b; 2、中国多个实体和个人被指涉伊朗军工制造将遭美国非法单边制裁&#xff0c;外交部&#xff1a;坚决反对&#xff…

2.3 如何使用FlinkSQL读取写入到JDBC(MySQL)

1、JDBC SQL 连接器 FlinkSQL允许使用 JDBC连接器&#xff0c;向任意类型的关系型数据库读取或者写入数据 添加Maven依赖 <dependency><groupId>org.apache.flink</groupId><artifactId>flink-connector-jdbc</artifactId><version>3.1…

杭电oj--字符串统计

对于给定的一个字符串&#xff0c;统计其中数字字符出现的次数。 输入数据有多行&#xff0c;第一行是一个整数n&#xff0c;表示测试实例的个数&#xff0c;后面跟着n行&#xff0c;每行包括一个由字母和数字组成的字符串。 对于每个测试实例&#xff0c;输出该串中数值的个…

【高危安全通告】Oracle 10月月度安全漏洞预警

近日&#xff0c;安全狗应急响应中心关注到Oracle官方发布安全公告&#xff0c;共披露出在Oracle Weblogic中存在的6个高危漏洞。 漏洞描述 CVE-2023-22069&#xff1a;Oracle Weblogic 远程代码执行漏洞 Oracle WebLogic Server存在远程代码执行漏洞&#xff0c;该漏洞的CVS…

如何写代码实现VRP问题中车辆容量限制及时间窗要求(python)

问题研究背景 使用遗传模拟退火算法求解如下10个卸货点的VRPTW问题。为了使研究的问题更加有意义&#xff0c;本人将时间限理解为服务点一天的具体可以允许配送的时间。 如果不要求车辆从配送中心出发的时间是统一的并且为0时刻&#xff0c;那么就默认第一个配送节点是一定能赶…

用echarts在vue2中实现3d饼图

先看效果&#xff0c;再看文章&#xff1a; 一、安装插件 3d的图不仅用到echarts&#xff0c;还用到了echarts-gl&#xff0c;因此都需要安装一下哦~ npm install echarts npm install echarts-gl2.0.9 //可以指定版本&#xff0c;也可不指定二、在main.js中引入 import * …

达索智能制造解决方案,敏捷电芯制造如何赋能企业竞争力 | 百世慧®

敏捷电芯制造赋能企业竞争力 全球电池市场正在快速扩大&#xff0c;为制造商带来巨大商机。 锂电行业的智能制造如何应用&#xff1f; 电池制造业的市场趋势是什么&#xff1f; 电池制造商面临哪些挑战&#xff1f; 特别是电池电芯制造方面&#xff0c;如何克服挑战获得竞…

竞赛 深度学习乳腺癌分类

文章目录 1 前言2 前言3 数据集3.1 良性样本3.2 病变样本 4 开发环境5 代码实现5.1 实现流程5.2 部分代码实现5.2.1 导入库5.2.2 图像加载5.2.3 标记5.2.4 分组5.2.5 构建模型训练 6 分析指标6.1 精度&#xff0c;召回率和F1度量6.2 混淆矩阵 7 结果和结论8 最后 1 前言 &…

微信小程序 —— 会议OA项目首页布局与Mock数据交互

14天阅读挑战赛如果世界上有奇迹&#xff0c;那一定是努力的另一个名字。 目录 一、小程序布局 1.1 Flex布局 1.2 Flex属性 二、OA会议首页搭建 2.1 首页底部菜单 2.2 创建后端结口 2.3 Mock模拟数据 2.4 首页轮播图搭建 2.5 首页内容搭建 一、小程序布局 1.1 Flex布…

产品新闻稿撰写流程是怎样的,纯干货

企业和品牌每年都会投放大量的产品新闻稿&#xff0c;一篇优质的产品新闻稿可以掀起更多的关注&#xff0c;收获更多的流量&#xff0c;如何在众多的新闻稿中脱颖而出&#xff0c;吸引读者的注意力&#xff1f;本文伯乐网络传媒将为您揭秘产品新闻稿撰写流程&#xff0c;教您如…

基于epoll封装非阻塞的reactor框架(附源码)

C++常用功能源码系列 文章目录 C++常用功能源码系列前言一、reactor架构二、client端reactor代码三、server端reactor代码四、单reactor架构可以实现百万并发总结前言 本文是C/C++常用功能代码封装专栏的导航贴。部分来源于实战项目中的部分功能提炼,希望能够达到你在自己的项…

学会C++之后,为什么学任何语言都会更加容易?

学会C之后&#xff0c;为什么学任何语言都会更加容易&#xff1f; 编程的本质是把自然语言翻译为机器语言&#xff0c;但机器没有联想力&#xff0c;所以它需要编写者事无巨细地告诉它怎么做。最近很多小伙伴找我&#xff0c;说想要一些c语言资料&#xff0c;然后我根据自己从业…

什么是美颜sdk?直播实时美颜sdk的工作流程和架构分析

在现代社交媒体和娱乐行业中&#xff0c;直播已经成为了一种受欢迎的娱乐形式&#xff0c;同时实时美颜也变得越来越重要。直播实时美颜SDK的工作流程和架构在这一领域起到了关键作用。本文将深入探讨这些SDK的内部机制&#xff0c;从而理解它们如何为用户提供出色的美颜效果。…

Pytest测试框架搭建的关键6个知识点(建议收藏)

在现代软件开发中&#xff0c;测试是确保代码质量和功能稳定性的关键步骤。而Pytest作为一个功能强大且易于使用的Python测试框架&#xff0c;为我们提供了一个优雅的方式来编写和管理测试。本文将为你介绍如何构建高效可靠的测试环境&#xff0c;着重探讨Pytest测试框架搭建时…

LinkedList 源码解析(JDK1.8)

目录 一. 前言 二. 常用方法 三. 源码解析 3.1. 属性和内部类 3.2. 构造函数 3.3. 添加元素 3.4. 获取元素 3.5. 删除元素 3.6. 迭代器 3.6.1. 头到尾方向的迭代 3.6.2. 尾到头方向的迭代 3.6.3. add() 插入元素 3.6.4. remove() 移除元素 一. 前言 LinkedList同时…

【每日一题Day353】LC2525根据规则将箱子分类 | 模拟

根据规则将箱子分类【LC2525】 给你四个整数 length &#xff0c;width &#xff0c;height 和 mass &#xff0c;分别表示一个箱子的三个维度和质量&#xff0c;请你返回一个表示箱子 类别 的字符串。 如果满足以下条件&#xff0c;那么箱子是 "Bulky"的&#xff1a…

TS使用echarts柱状图鼠标放上去并弹出

效果 代码 <template><div><Chart style"width: 100%; height: 344px" :option"chartOption" /></div> </template><script lang"ts" setup>import { ref } from vue;import { ToolTipFormatterParams } f…

Keeplived安装部署(单机双机)

Keeplived官网&#xff1a;https://www.keepalived.org/download.html 一 单机安装配置: 1.上传keepalived安装包并且安装 [rootmaster1 local]# tar -zxvf keepalived-2.2.8.tar.gz [rootmaster1 local]# mv keepalived-2.2.8 keepalived [rootmaster1 local]# chown root:r…