AbstractQueuedSynchronizer之AQS
# AbstractQueuedSynchronizer 之 AQS
AbstractQueuedSynchronizer 字面意思抽象的队列同步器

- AbstractOwnableSynchronizer
- AbstractQueuedLongSynchronizer
- AbstractQueuedSynchronizer 简称为 AQS
AbstractQueuedSynchronizer 是用来构建锁或者其它同步器组件的重量级基础框架及整个 JUC 体系的基石,通过内置的 FIFO 队列来完成资源获取线程的排队工作,并通过一个 int 类变量表示持有锁的状态。

CLH:Craig、Landin and Hagersten 队列,是一个单向链表,AQS 中的队列是 CLH 变体的虚拟双向队列 FIFO
# AQS 为什么是 JUC 内容中最重要的基石
和 AQS 有关的

ReentrantLock

CountDownLatch

ReentrantReadWriteLock

Semaphore

# 进一步理解锁和同步器的关系
- 锁,面向锁的使用者:定义了程序员和锁交互的使用层 API,隐藏了实现细节,你调用即可。
- 同步器,面向锁的实现者:比如 Java 并发大神 DougLee,提出统一规范并简化了锁的实现,屏蔽了同步状态管理、阻塞线程排队和通知、唤醒机制等。
# 能干什么
加锁会导致阻塞,有阻塞就需要排队,实现排队必然需要队列
抢到资源的线程直接使用处理业务,抢不到资源的必然涉及一种排队等候机制。抢占资源失败的线程继续去等待 (类似银行业务办理窗口都满了,暂时没有受理窗口的顾客只能去候客区排队等候),但等候线程仍然保留获取锁的可能且获取锁流程仍在继续 (候客区的顾客也在等着叫号,轮到了再去受理窗口办理业务)。
既然说到了排队等候机制,那么就一定会有某种队列形成,这样的队列是什么数据结构呢?
如果共享资源被占用,就需要一定的阻塞等待唤醒机制来保证锁分配。这个机制主要用的是 CLH 队列的变体实现的,将暂时获取不到锁的线程加入到队列中,这个队列就是 AQS 的抽象表现。它将请求共享资源的线程封装成队列的结点(Node),通过 CAS、自旋以及 LockSupport.park () 的方式,维护 state 变量的状态,使并发达到同步的效果。

# AQS 初步
# AQS 初识

有阻塞就需要排队,实现排队必然需要队列,AQS 使用一个 volatile 的 int 类型的成员变量来表示同步状态,通过内置的 FIFO 队列来完成资源获取的排队工作将每条要去抢占资源的线程封装成一个 Node 节点来实现锁的分配,通过 CAS 完成对 State 值的修改。
# AQS 内部体系架构
# AQS 自身
# AQS 的 int 变量
AQS 的同步状态 State 成员变量

银行办理业务的受理窗口状态
- 零就是没人,自由状态可以办理
- 大于等于 1,有人占用窗口,等着去
# AQS 的 CLH 队列
CLH 队列 (三个大牛的名字组成),为一个双向队列

银行候客区的等待顾客
# 总结
- 有阻塞就需要排队,实现排队必然需要队列
- state 变量 + CLH 双端队列
# 内部类 Node (Node 类在 AQS 类内部)
# Node 的 int 变量
Node 的等待状态 waitState 成员变量

等候区其它顾客 (其它线程) 的等待状态,队列中每个排队的个体就是一个 Node
Node 内部结构

属性说明

# AQS 同步队列的基本结构

CLH:Craig、Landin and Hagersten 队列,是个单向链表,AQS 中的队列是 CLH 变体的虚拟双向队列(FIFO)
# 从 ReentrantLock 开始解读 AQS
Lock 接口的实现类,基本都是通过【聚合】了一个【队列同步器】的子类完成线程访问控制的
# ReentrantLock 的原理

# 从最简单的 lock 方法开始看看公平和非公平




可以明显看出公平锁与非公平锁的 lock () 方法唯一的区别就在于公平锁在获取同步状态时多了一个限制条件: hasQueuedPredecessors() hasQueuedPredecessors 是公平锁加锁时判断等待队列中是否存在有效节点的方法

# 非公平锁走起,方法 lock ()
本次讲解我们走非公平锁作为案例突破口
lock()

acquire()


tryAcquire(arg) ,本次走非公平锁

下一步:

nonfairTryAcquire(acquires)

- return false; 继续推进条件,走下一个方法
- return true; 结束
addWaiter(Node.EXCLUSIVE)

enq(node);

双向链表中,第一个节点为虚节点 (也叫哨兵节点),其实并不存储任何信息,只是占位。真正的第一个有数据的节点,是从第二个节点开始的。
假如 3 号 ThreadC 线程进来
- prev
- compareAndSetTail
- next
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)

假如再抢抢失败就会进入
shouldParkAfterFailedAcquire 和 parkAndCheckInterrupt 方法中

shouldParkAfterFailedAcquire
如果前驱节点的 waitStatus 是 SIGNAL 状态,即 shouldParkAfterFailedAcquire 方法会返回 true 程序会继续向下执行 parkAndCheckInterrupt 方法,用于将当前线程挂起
parkAndCheckInterrupt

# unlock
- sync.release(1);
- tryRelease(arg)
- unparkSuccessor