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

AQS的详细讲解

前言:

标题:深入并发核心:解锁 Java AQS 的奥秘

在 Java 并发编程的世界里,AbstractQueuedSynchronizer (AQS) 宛如一位低调而强大的幕后导演。它是 ReentrantLockSemaphoreCountDownLatch 等众多重量级同步工具类得以高效运转的基石。理解 AQS,就如同拿到了解开 Java 高并发同步机制底层运作原理的钥匙。

你是否曾好奇过,锁是如何实现公平与非公平的?信号量如何精确控制资源访问?共享锁与独占锁的队列管理有何精妙之处?答案,就藏在这看似复杂、实则设计精良的 AQS 框架之中。

这篇博客将带你深入 AQS 的核心,从基础概念到核心实现(CLH 队列、状态管理、获取/释放逻辑),逐步剖析其精妙的设计哲学。准备好迎接挑战了吗?让我们一起揭开 AQS 的神秘面纱,探索 Java 并发编程的底层力量!虽然内容可能有些硬核,但相信收获的知识绝对值得投入。


一、什么是AQS:

AQS是一个并发抽象工具类,用于构建锁,同步器,协作工具类的框架。

如何设计一个简单的并发工具类

1.保证他的线程安全:

那你就需要知道,共享资源什么时候空闲,什么时候被占用?

解决方法就是:设置一个state变量,要是为0,那就表示线程空闲,不为0表示线程占用

那此时state变量是为布尔类型吗?

state应该设计成一个int类型的值,因为有时不止一个线程会使用这个共享资源,int类型会更加的灵活,比如有三个线程访问,那就把state赋值为3。

 

如何保证state的可见性呢?

volatile关键字来对state进行修饰。保证state的值更新时能够实时的被所有线程看到。

volatile保证可见性的底层原理?

他是通过底层的cpu缓存一致性协议,来实现多核缓存的之间的数据同步。

可见性ok了,那原子性如何保证?

通过CAS机制,去修改state的值

CAS机制(Compare and Swap:比较并交换)是什么?

举个上厕所的例子:厕所就是共享资源,你和你朋友都想上厕所,你想上厕所,那你得看厕所上的标识是有人还是没人(这个就是比较的过程,比较自己想上厕所的状态是否和厕所的状态匹配)然后你发现厕所空闲,然后你进去了,把门锁住,厕所标识为有人(这个就是交换的过程,将自己的old value和厕所的new value交换)然后你的朋友在外面急得团团转,每隔一分钟就问你:好了吗?(这个就是自旋)

发现了吗?这个过程其实是没有加锁的,这也是乐观锁的实现例子

乐观锁和悲观锁是什么?

这俩个并不是锁,而是一种思想,是一种机制。

乐观锁就是乐观的认为:并发冲突不会发生不需要加锁,而是通过各种机制办法,来避免冲突。如果发生了并发冲突,我们可以通过报错、异常的方式来解决

悲观锁就是悲观地认为:冲突一定会发生,所以是读也加锁,写也加锁,安全是安全,但是慢啊。每一步都谨小慎微,肯定就慢了

比较并交换,他其实是两个操作,那就会有原子性的问题(要么全成功要么全失败)那如何保证呢?

可不能加锁啊,CAS都是无锁状态,他其实是通过底层的硬件来实现的(锁住总线,锁住CPU的缓存行,这样就能保证一致性

2.没有获取到共享资源的线程该怎么办?一直自旋吗?

那太浪费性能了,而且如果线程非常多的话,如何的高效管理这些线程呢?我们可以将线程看作是一个个Node节点,让他们组成一个双向链表,基于这个双向链表组成一个先进先出的队列,这个队列就叫做FIFO队列。

为什么这个地方要设计成双向链表呢?

1:高效的移除无效节点

有些节点因为中断和超时,他会失效,此时我们遍历到无效节点时移除这个节点,直接可以prev方法到上一个节点,然后next下一个有效节点就完成移除无效节点了

把未抢夺到资源的线程全都放到等待队列里,用wait()方法让他们阻塞等待,等共享资源的锁释放了,再唤醒线程。

当共享资源的锁释放后,等待队列中的线程是全部唤醒,还是一个一个唤醒,还是一次批量唤醒?

独占模式和共享模式:

独占模式:每次只唤醒一个线程,因为只能有一个线程访问共享资源。

共享模式:允许几个线程访问共享资源,那就唤醒几个线程

举例:Semaphore或者CountdownLatch

如果说某个业务,他只是想要获取一下共享资源,获取不到就去干别的事情,而不是被阻塞然后塞进队列,那这个该咋办呢?

两个方法:tryAcquire(),acquire()

tryAcquire()方法:返回true或者false,表示资源获取有没有成功

acquire()方法:一直访问共享资源,访问不到就阻塞,等待下次访问。

所以只需要先调用tryAcquire()这个方法,判断是否成功,成功我就拿资源,失败我就干别的事去了。

ReentrantLock中,没有拿到锁的线程被放到等待队列中,拿到锁的线程需要等待资源到位,调用await方法之后,应该被放到Condition的条件队列中

那为什么要还搞一个等待队列呢?

主要是公平机制,队列本身就是先进先出的,天然的是公平的,那AQS的实现类那就可以直接重写tryAquire()这个方法,就可以自定义公平和非公平策略了

ReentrantLock()有两个FairSyncNonFairSync的子类,他们都重写了AQS的tryAquire()方法,

FairSync是依赖了队列的结构:实现了先来后到,保证了公平机制

NonFairSync就实现了插队的功能,实现了非公平机制

Synchronized是有锁池和等待池的,锁池用来放获取锁失败的线程,等待池是用来放线程通信时主动放弃锁的线程

ReentrantLock中,没有拿到锁的线程被放到等待队列中,拿到锁的线程需要等待资源到位,调用await方法之后,应该被放到Condition的条件队列中

这里就引申出来了:ReentrantLock和Synchronized的区别

  1. ReentrantLock是基于AQS实现的,有tryAcquire()方法,所以他会有tryLock()方法,尝试加锁,加锁失败他可以做其他的操作。
  2. RenntarntLock基于AQS的tryAquire()方法实现了公平锁和非公平锁的机制,而SynchronSize却没有
  3. Synchronized是有锁池和等待池的,锁池用来放获取锁失败的线程,等待池是用来放线程通信时主动放弃锁的线程,ReentrantLock中,没有拿到锁的线程被放到等待队列中,拿到锁的线程需要等待资源到位,调用await方法之后,应该被放到Condition的条件队列中

结尾 (结语):

动手实践是最好的学习:强烈建议你打开 JDK 源码(特别是 java.util.concurrent.locks.AbstractQueuedSynchronizer),结合本文的讲解,亲自去跟踪一次 acquire() 或 release() 的调用流程,感受线程入队、出队、状态变化的每一个细节。纸上得来终觉浅,绝知此事要躬行。

希望这篇关于 AQS 的探讨,能为你照亮 Java 并发编程道路上的一个重要里程碑!你对 AQS 的理解如何?在实际工作中是否遇到过需要深入 AQS 的场景?欢迎在评论区留下你的见解、疑问或经验分享!

http://www.dtcms.com/a/324345.html

相关文章:

  • Java对接支付宝,回调验签失败
  • 活动策划(展会、年会),在线工具能快速出邀请函不?
  • [创业之路-537]:经营分析会 - 销售目标以及支撑、关键策略、主要行动措施、资源保障、人才储备
  • 在 JDK 17 上完整观察 synchronized 锁升级过
  • 嵌入式第二十四课!!linux应用软件编程与文件操作!!!
  • Java 基础编程案例:斐波拉契数与从输入交互到逻辑处理
  • NodeJs学习日志(4):路由合并_环境配置_常用文件目录
  • HarmonyOS之module.json5功能详解
  • AI测试助手如何让Bug无处可藏
  • 湖南(源点咨询)市场调研 如何在行业研究中快速有效介入 中篇
  • 深入浅出DBSCAN:基于密度的聚类算法详解与Python实战
  • github上传文件
  • Navicat 无限适用
  • Tesseract训练个人字库操提高准确率操作全流程(详细)
  • 新手向:Python制作简易音乐播放器
  • Python中的 __name__
  • 遇到前端导出 Excel 文件出现乱码或文件损坏的问题
  • 异或循环冗余
  • Python设计模式 - 装饰模式
  • 新手向:Python实现文件加密解密工具
  • 旅行者1号无线电工作频段
  • 18.3 全量微调:数据预处理之清洗与准备
  • 机器学习——DBSCAN 聚类算法 + 标准化
  • 实现两个开发板的串口通讯(基于STC8实现)
  • 复刻苏宁易购(移动端)
  • 【GPT入门】第44课 检查 LlamaFactory微调Llama3的效果
  • cursor, vscode黄色波浪线警告问题
  • React:useEffect 与副作用
  • 小巧实用的工具——ZoomIt
  • 【C++对象诞生全解析】构造函数:从内存布局到高效初始化的终极指南