AQS维护了一个共享资源状态(用volatile int State表示),并通过CAS操作完成对State值的修改。它使用一个先进先出(FIFO)的线程等待队列来完成资源获取的排队工作。当有自定义同步器接入时,只需重写第一层所需要的部分方法即可,不需要关注底层具体的实现流程。同时,Java中的大部分同步类(Lock、Semaphore、ReentrantLock等)都是基于AbstractQueuedSynchronizer(简称为AQS)实现的。
AQS 总体结构图
AQS 中 维护了一个 volatile 修饰的 state 变量(代表共享资源)和一个 FIFO 线程等待队列(多线程争用资源被阻塞时会进入此队列)通过 CAS来操作 state 保证在多线程情况下的安全性
java复制代码
private transient volatile Node head;
/**
* Tail of the wait queue, lazily initialized. Modified only via
* method enq to add new wait node.
*/
private transient volatile Node tail;
/**
* The synchronization state.
*/
private volatile int state;
每次仅允许等待队列的队头去访问 state ,因此可以保证每次仅有一个线程操作 state 变量。那么是如何保证仅允许队头元素去访问 state 呢?是通过 CAS 吗?
ReentrantLock
由于 AQS 对锁的操作没有实现,因此我们通过分析 ReentrantLock 中的加锁代码
java复制代码
// ReentrantLock.java 文件
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1)) // 因为是非公平锁,所以在一开始的时候会先尝试获取锁,获取失败再与等待队列的线程进行争抢
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
static final class FairSync extends Sync { // 公平锁
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() && // 判断是否有等待队列,如果有等待队列且队首元素不是当前线程,那么便不会进行锁的争抢
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) { // 如果锁的持有者是当前线程,则可以重复加锁并且由于已经加锁了不会再出现多线程情况,因此不需要多线程控制
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) { // 如果此时没有线程获取锁,那么当前线程可以再次尝试获取锁
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) { // 如果锁的持有者是当前线程,则可以重复加锁并且由于已经加锁了不会再出现多线程情况,因此不需要多线程控制
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false; // 获取锁失败
}
java复制代码
// AbstractQueuedSynchronizer.java
public final void acquire(int arg) {
if (!tryAcquire(arg) && // FairSync 和 UnfairSync 有各自的实现
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 获取锁失败时,才会走到这,将其加入等待队列队尾,并设置唤醒标记给前一个节点
selfInterrupt(); // 进入阻塞状态
}
整个加锁的逻辑
非公平锁:
尝试获取锁(没有线程加锁)
再次尝试获取锁(没有线程加锁)
重复加锁(加锁线程是当前线程)
加入等待队列,并将唤醒标记给等待队列的前一个节点
自身进入阻塞状态
公平锁:
尝试加锁(没有线程加锁,等待队列为空或等待队列对头线程为当前线程)
重复加锁(加锁线程是当前线程)
释放锁的逻辑:
尝试释放锁
释放锁成功后从队头元素开始往后唤醒阻塞的节点,如果后继节点被取消了,那么会从后往前开始唤醒
为什么会从后往前唤醒呢?
这是因为由于 next 指针的不可靠性所决定的,我们来看一下 enq 方法
java复制代码
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t; // 可以多个线程都执行完此方法
if (compareAndSetTail(t, node)) { // 由于 CAS 的存在,只会存在一个线程执行完该方法,最终会导致 多个线程的 prev 指向了 t 。但是 t 的后驱只有一个
t.next = node;
return t;
}
}
}
}