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

【并发编程】AQS原理

       📝个人主页:五敷有你      

 🔥系列专栏:并发编程

⛺️稳中求进,晒太阳

1. 概述

全称是 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)) { 
// 让阻塞线程恢复运行
}

2. 实现不可重入锁

自定义同步器

//同步器类
class MySync extends AbstractQueuedSynchronizer {@Overrideprotected boolean tryAcquire(int arg) {if (compareAndSetState(0,1)) {setExclusiveOwnerThread(Thread.currentThread());return true;}return false;}@Overrideprotected boolean tryRelease(int arg) {setExclusiveOwnerThread(null);setState(0);return true;}@Override //是否持有独占锁protected boolean isHeldExclusively() {if(getExclusiveOwnerThread()!=null){return true;}return false;}public Condition newCondition(){return  new ConditionObject();}}

自定义锁

有了自定义同步器,很容易复用 AQS ,实现一个功能完备的自定义锁

public class MyLock implements Lock {MySync mySync=new MySync();@Override  //加锁,不成功进入等待队列等待
public void lock() {mySync.acquire(1);}@Override //加锁,可以被打断
public void lockInterruptibly() throws InterruptedException {mySync.acquireInterruptibly(1);
}@Override //尝试加锁(一次)
public boolean tryLock() {mySync.tryAcquire(1);return true;
}@Override //尝试加锁(带超时时间)
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {mySync.tryAcquireNanos(1,unit.toNanos(time));return true;
}@Override //解锁
public void unlock() {mySync.release(1);
}@Override  //创建条件变量
public Condition newCondition() {return mySync.newCondition();
}}

测试一下

public static void main(String[] args) {Lock myLock=new MyLock();new Thread(()->{myLock.lock();try {System.out.println("locking...");Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}finally {System.out.println("unlock...");myLock.unlock();}},"t1").start();new Thread(()->{myLock.lock();try {System.out.println("加锁...");}finally {System.out.println("解锁...");myLock.unlock();}},"t2").start();}

心得

起源

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

目标

AQS 要实现的功能目标

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

设计

AQS 的基本思想其实很简单

获取锁的逻辑

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

释放锁的逻辑

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

要点

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

主要用到AQS的并发工具类

相关文章:

  • LCP 30. 魔塔游戏
  • Bean 的六种作用域
  • Flink理论—容错之状态
  • 【报错解决】-bash: export: `-8‘: not a valid identifier 不是有效的标识符
  • 【算法题】93. 复原 IP 地址
  • HCIA-HarmonyOS设备开发认证V2.0-轻量系统内核基础-消息队列queue
  • 【Linux学习】线程池
  • 【数据库】哪些操作会导致索引失效
  • Java 中 一些常见的并发集合类
  • 『运维备忘录』之 Zip 命令详解
  • C#系列-使用 Minio 做图片服务器实现图片上传 和下载(13)
  • 【MySQL】:深入理解并掌握DML和DCL
  • [OPEN SQL] 修改数据
  • 算法专题:线性DP
  • antdpro框架npm install 报错,切换tyarn安装成功。
  • 《Javascript高级程序设计 (第三版)》第五章 引用类型
  • electron原来这么简单----打包你的react、VUE桌面应用程序
  • JS数组方法汇总
  • MyEclipse 8.0 GA 搭建 Struts2 + Spring2 + Hibernate3 (测试)
  • scrapy学习之路4(itemloder的使用)
  • Webpack4 学习笔记 - 01:webpack的安装和简单配置
  • yii2权限控制rbac之rule详细讲解
  • 从0搭建SpringBoot的HelloWorld -- Java版本
  • 关于Java中分层中遇到的一些问题
  • 聊聊flink的TableFactory
  • 那些被忽略的 JavaScript 数组方法细节
  • 爬虫模拟登陆 SegmentFault
  • 设计模式走一遍---观察者模式
  • 突破自己的技术思维
  • 网页视频流m3u8/ts视频下载
  • 我从编程教室毕业
  • 新版博客前端前瞻
  • [地铁译]使用SSD缓存应用数据——Moneta项目: 低成本优化的下一代EVCache ...
  • 我们雇佣了一只大猴子...
  • #多叉树深度遍历_结合深度学习的视频编码方法--帧内预测
  • $con= MySQL有关填空题_2015年计算机二级考试《MySQL》提高练习题(10)
  • (27)4.8 习题课
  • (39)STM32——FLASH闪存
  • (4)(4.6) Triducer
  • (C语言)共用体union的用法举例
  • (板子)A* astar算法,AcWing第k短路+八数码 带注释
  • (翻译)Quartz官方教程——第一课:Quartz入门
  • (论文阅读30/100)Convolutional Pose Machines
  • (亲测成功)在centos7.5上安装kvm,通过VNC远程连接并创建多台ubuntu虚拟机(ubuntu server版本)...
  • *p=a是把a的值赋给p,p=a是把a的地址赋给p。
  • .Net MVC4 上传大文件,并保存表单
  • .NET 材料检测系统崩溃分析
  • .NET 使用 JustAssembly 比较两个不同版本程序集的 API 变化
  • [ Linux 长征路第二篇] 基本指令head,tail,date,cal,find,grep,zip,tar,bc,unname
  • [ 第一章] JavaScript 简史
  • []FET-430SIM508 研究日志 11.3.31
  • [2013AAA]On a fractional nonlinear hyperbolic equation arising from relative theory
  • [AutoSar]BSW_Com07 CAN报文接收流程的函数调用
  • [hdu 1711] Number Sequence [kmp]
  • [JavaWeb]—Spring入门