本文最后更新于:2024年4月22日 下午
AQS
即是抽象的队列式的同步器,熟知的ReentrantLock
、ReentrantReadWriteLock
、CountDownLatch
、Semaphore
等都是基于AQS
来实现的。
基本原理
是用来构建锁或者其它同步器组件的重量级基础框架及整个JUC体系的基石,通过内置的FIFO队列来完成资源获取线程的排队工作,并通过一个int类变量表示持有锁的状态
图片来自网络
CLH:Craig、Landin and Hagersten 队列,是一个单向链表,AQS中的队列是CLH变体的虚拟双向队列FIFO
AQS
中 维护了一个volatile int state
(代表共享资源)和一个FIFO
线程等待队列(多线程争用资源被阻塞时会进入此队列)
这里volatile
能够保证多线程下的可见性,当state=1
则代表当前对象锁已经被占有,其他线程来加锁时则会失败,加锁失败的线程会被放入一个FIFO
的等待队列中,比列会被UNSAFE.park()
操作挂起,等待其他获取锁的线程释放锁才能够被唤醒
另外state
的操作都是通过CAS
来保证其并发修改的安全性
AQS的基本API
- getState():获取锁的标志state值
- setState():设置锁的标志state值
- tryAcquire(int):独占方式获取锁。尝试获取资源,成功则返回true,失败则返回false
- tryRelease(int):独占方式释放锁。尝试释放资源,成功则返回true,失败则返回false
AQS源码
以ReentrantLock为例,加锁过程
1、尝试加锁
2、加锁失败,线程放入队列
3、线程入队列后,进入阻塞状态
非公平锁NonfairSync为例,案例都是理想正常流程执行 A-B-C 线程执行,实际上有差异
Lock方法(挂起线程)
compareAndSetState
比较state,这里A先进入比较并交换 设置state
0到1,AQS
实现,
此时B线程执行lock
开始抢占锁,此时非常抱歉,此时state=1
,那么B只能进入acquire
方法
模板方法进入AbstractQueuedSynchronizer
的acquire方法,注意:tryAcquire 方法AbstractQueuedSynchronizer本身不实现,会抛出UnsupportedOperationException异常
而非公平NonfairSync里面的实现
由于此时C=1,并且getExclusiveOwnerThread=A ,所以此时返回false
接下来执行父类模板定义的方法
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
此时tail=null,enq 自旋先创建哨兵节点
,用于占位,再创建b节点入队,并将Node 头尾节点设置
for (;;)这里又是一个自旋,
第一次自旋,tryAcquire去抢锁,这里默认A 休眠五分钟,所以这里肯定抢占失败返回false,执行shouldParkAfterFailedAcquire 将p也就是当前的哨兵节点,此时哨兵节点pred的waitStatus=0,条件进入比较并交换为-1。
第二次自旋,shouldParkAfterFailedAcquire 哨兵节点pred的waitStatus=-1,此方法返回true,便执行parkAndCheckInterrupt中的方法,此时就把线程B挂起
unLock 方法(唤醒线程)
unlock—>sync.release(1)—>tryRelease 子类实现
tryRelease=0,返回true,执行unparkSuccessor,此时node是头节点是哨兵节点 waitStatus=-1,下一节点B将执行
LockSupport.unpark(s.thread)。也就是B节点线程唤醒