AQS简介
AQS全名:AbstractQueuedSynchronizer,是并发容器J.U.C(java.lang.concurrent)下locks包内的一个类。它实现了一个FIFO的队列。底层实现的数据结构是一个双向列表。
Sync queue:同步队列,是一个双向列表。包括head节点和tail节点。head节点主要用作后续的调度。
Condition queue:非必须,单向列表。当程序中存在cindition的时候才会存在此列表。
AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配。
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable{...}
public abstract class AbstractOwnableSynchronizer
implements java.io.Serializable {
private static final long serialVersionUID = 3737899427754241961L;
protected AbstractOwnableSynchronizer() { }
// 独占模式下的线程
private transient Thread exclusiveOwnerThread;
// 设置独占资源线程
protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}
// 获取独占资源程
protected final Thread getExclusiveOwnerThread() {
return exclusiveOwnerThread;
}
}
AQS 核心思想
如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。
如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配
的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配。
AQS设计原理
- 使用Node实现FIFO队列,可以用于构建锁或者其他同步装置的基础框架。
- 利用int类型标识状态。在AQS类中有一个叫做state的成员变量(
private volatile int state;
),表示获取锁的线程数(state=0,表示还没有线程获取锁,1表示有线程获取了锁。大于1表示重入锁的数量)和一个同步组件ReentrantLock。
//返回同步状态的当前值
protected final int getState() {
return state;
}
// 设置同步状态的值
protected final void setState(int newState) {
state = newState;
}
/**
* Atomically sets synchronization state to the given updated
* value if the current state value equals the expected value.
* This operation has memory semantics of a {@code volatile} read
* and write.
* @param expect the expected value
* @param update the new value
*/
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
- 使用方法是继承,
基于模板方法
。
//使用者继承AbstractQueuedSynchronizer并重写指定的方法
isHeldExclusively()//该线程是否正在独占资源。只有用到condition才需要去实现它。
tryAcquire(int)//独占方式。尝试获取资源,成功则返回true,失败则返回false。
tryRelease(int)//独占方式。尝试释放资源,成功则返回true,失败则返回false。
tryAcquireShared(int)//共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
tryReleaseShared(int)//共享方式。尝试释放资源,成功则返回true,失败则返回false。
- 子类通过继承并通过实现它的方法管理其状态(acquire和release方法操纵状态)。
- 可实现排它锁和共享锁模式
(独占、共享)
。AQS的功能主要分为两类:独占和共享。子类,要么实现并使用了它的独占功能的api,要么使用了共享锁的功能,而不会同时使用两套api,即便是最有名的子类ReentrantReadWriteLock也是通过两个内部类读锁和写锁分别实现了两套api来实现的。
可重入锁,指对同一个ReentrantLock对象多次执行lock()加锁和unlock()释放锁。
AQS实现思路
- AQS内部维护了一个CLH队列来管理锁。
- 线程会首先尝试获取锁,如果失败就将当前线程及等待状态等信息包装成一个node节点加入到同步队列sync queue里。
- 不断的重新尝试获取锁(当前节点为head的直接后继才会尝试)。如果失败就会阻塞自己,直到自己被唤醒。
- 当持有锁的线程释放锁的时候,会唤醒队列中的后继线程。
图片参考:大白话聊聊Java并发面试问题之谈谈你对AQS的理解?【石杉的架构笔记】
两种资源获取方式:独占
(只有一个线程能访问执行,又根据是否按队列的顺序分为公平锁和非公平锁,如ReentrantLock) 和共享
(多个线程可同时访问执行,如Semaphore、CountDownLatch、 CyclicBarrier )。ReentrantReadWriteLock可以看成是组合式,允许多个线程同时对某一资源进行读。