一、AQS 是什么
- Abstract Queued Synchronizer ,翻译过来就是“抽象的排好队的同步器”。 AQS 是一个用来构建锁和同步器的框架。
- 是用来构建锁或者其他同步器组件的重量级基础框架及整个JUC体系的基石,通过内置的FIFO队列来完成线程获取资源的排队工作,并通过一个int类型变量表示持有锁的状态。
二、AQS 有什么用
- 使用 AQS 能简单高效的构造出大量被广泛应用的同步器,比如 ReentrantLock,Semaphore,ReentrantReadWriteLock,SynchronousQueue,FutureTask等等,都是基于 AQS 的。
- 我们也可以使用 AQS 轻松构造出符合特定需求的同步器。
- 加锁会导致阻塞,有阻塞就需要排队,实现排队必然需要有某种形式的队列(CLH)来进行管理
三、AQS 的核心思想
- 如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,
- 并且将共享资源设置为锁定状态。
- 如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制
- 这个机制是 AQS 使用 CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
- CLH 是一个虚拟的双向FIFO队列,AQS 将每个请求共享资源的线程封装成Node 节点,并放入CLH 中
- AQS 使用一个volatile 的 int 类型的变量来表示同步状态,通过内置的FIFO队列来完成资源获取的排队工作将每条要去抢占资源的线程封装成一个Node节点来实现锁的分配,通过CAS完成对State值的修改
四、AQS 基本结构
五、AQS 怎么用
- 创建任一基于AQS实现的同步器
- 创建多个线程,并分别使用同步器的 lock 和 unlock 方法
- 会发现,这些线程是一个一个执行的,而非一起执行的,说明锁成功了。
六、进一步理解锁和同步器的关系
- 锁,面相锁的使用者,定义了程序员和锁交互的使用层API
- 同步器,面相锁的实现者,
七、源码分析
- Lock --> Syn -----> AQS,所以Lock 最终继承的是AQS
- lock(),acquire(),tryAcquire(arg),addWaiter(Node.EXCLUSIVE),addQueued(addWaiter(Node.EXCLUSIVE),arg)
- head和tail
- 是什么?
head 和 tail 分别是头指针和尾指针,分别指向队列中的头节点和尾节点 - 有什么用?
方便出队和入队操作,也方便逻辑判断(暂时是这么理解的)
- 是什么?
- 哨兵节点有什么用?
队列中的哨兵节点在并发编程中起到了重要的作用。哨兵节点是一种特殊的节点,通常用于帮助管理和控制并发队列的行为。在使用队列实现同步器时,哨兵节点可以用来简化逻辑、提高效率和确保线程安全。 - 确实有点复杂,但我感觉不应该去理解源码的每一行代码,而应该从整体设计思想去理解就好了,以后有机会自己实现一个简陋的example。
- 出队操作:第一个真实节点B抢占资源成功,哨兵节点出队,B变成新的哨兵节点
参考博客 一文让你彻底搞懂AQS(通俗易懂的AQS)
声明:仅用于学习,不做其他用途。本文对所引用的博客,进行了摘抄、整理,其中AQS 的知识点并不详尽,希望未来有机会能够更加深入地讲解它。
扩展
一、可重入锁
- 可重入锁,又名递归锁。是指在同一个线程,在外层方法获取锁的时候,再进入线程的内层方法时,会自动获取锁(前提,锁对象得是同一个对象),不会因为之前已经获取过还没释放而阻塞
- Java 中 ReentrantLock 和 Synchronized 都是可重入锁
- 可重入锁的一个优点是可以一定程度避免死锁
- 一句话概括:同一个线程可以多次获得属于自己的同一把锁,一定程度避免死锁
二、 LockSupport
- 是什么
LockSupport 用于创建锁和其他同步类的基本线程阻塞原语。 - 有什么用
- 对于原有锁支持线程等待唤醒机制(wait/nofify)的加强、改良版
- LockSupport 的 park()和 unpark()的作用分别是阻塞线程和解除阻塞线程
- 两个三角形:
- (synchronized,wait,notify)
- (lock,await,signal)
- 核心思想
- LockSupport 使用了一种名为 Permit (许可)的概念来做到阻塞和唤醒线程的功能,每个线程都有一个许可(permit),permit 只有两个值1和0,默认为零
- 可以把许可看成一种(0,1)信号量(Semaphore),但是与Semaphore 不同的是,许可的累加上限是1
- 怎么用
- LockSupport.park() 阻塞
- LockSupport.unpark(a) 唤醒啊
- 什么叫阻塞,什么叫唤醒?
- 阻塞就是说,在那一步线程就转换为“等待执行”的状态
- 唤醒就是说,让线程继续执行
- unpark()可以在park()之前执行
- 优点
LockSupport 是一个线程阻塞工具类,所有的方法都是静态方法,可以让线程在任意位置阻塞,阻塞之后也有对应的唤醒方法。归根结底,LockSupport 调用 Unsafe 中的native 代码。 - 为什么可以先唤醒线程后阻塞线程?
因为unpark 获得了一个凭证,之后,在调用 park 方法,就可以名正言顺的凭证消费,故不会阻塞。