一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第13天,点击查看活动详情。
1.什么是AQS
Java中的大部分同步类(Lock、Semaphore、ReentrantLock等)都是基于AbstractQueuedSynchronizer(简称为AQS)实现的。AQS是一种提供了原子式管理同步状态、阻塞和唤醒线程功能以及队列模型的简单框架。
2.AQS类的结构
AQS类的结构
head 头指针
tail 尾指针
state 状态
Node结构如下
相关解释
3.AQS的基本原理
AQS核心思想是,如果被请求的共享资源空闲,那么就将当前请求资源的线程设置为有效的工作线程,将共享资源设置为锁定状态;如果共享资源被占用,就需要一定的阻塞等待唤醒机制来保证锁分配。这个机制主要用的是CLH队列的变体实现的,将暂时获取不到锁的线程加入到队列中。
CLH:Craig、Landin and Hagersten队列,是单向链表,AQS中的队列是CLH变体的虚拟双向队列(FIFO),AQS是通过将每条请求共享资源的线程封装成一个节点来实现锁的分配。
主要原理图如下:
4.从ReentrantLock的实现看AQS的原理
假设有三个线程都要获取lock
抢到锁的线程 会将state状态由0改为1,这个时候其他线程没抢到 就会执行挂起抢占逻辑
也就是下图的 acquire()方法
我们打开查看acquire方法
按循序执行了三个方法
tryAcquire(arg)
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
第一个方法 tryAcquire尝试抢占:
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//状态为0直接抢占到
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;
}
复制代码
正如上文所说的 第一个线程已经将state由0改成了1 并且在执行中,第二个线程此时 抢不到返回为false
接着就会执行addWaiter()方法
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
复制代码
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)) {
t.next = node;
return t;
}
}
}
}
复制代码
由于第二个线程是第一个进来抢占的 所以tail为空 需要先构建双向队列 执行enq方法 该方法先创建了一个哨兵节点作为head 然后将第二个线程封装的节点放在了其后面 尾指针指向第二个线程的节点 如下图所示
接下来 会进入acquireQueued()方法
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
//上一个节点为头节点 尝试抢占
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//由于第一个线程还未释放锁 state状态还是1 所以抢占失败 进入下面逻辑
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
复制代码
第二个线程节点上一个节点为头节点 尝试抢占,由于第一个线程还未释放锁 state状态还是1 所以抢占失败 进入下面逻辑shouldParkAfterFailedAcquire(p, node)&& parkAndCheckInterrupt()
先看第一个方法
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
复制代码
进入的时候节点的前一个节点waitstatus为0 将waitstatus状态改为-1表示进入阻塞 返回false 接着上一步的自旋,此时又抢占失败 进入了这个方法,由于waitstatus状态已经为-1 所以返回true 进入第二个方法 parkAndCheckInterrupt()
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
复制代码
这个方法使用LockSupport.park(this) 精准park了这个线程 此时 线程二阻塞在这一行代码。
就在此时线程一跑完了 执行了unlock方法 我们来看下unlock方法
public void unlock() {
sync.release(1);
}
复制代码
使用下面方法释放锁 将setExclusiveOwnerThread设置为null state设置为0
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
复制代码
将state改为0之后 拿到队列的头节点 由于头节点在上面已经不为空 并且waitstatus被改为了-1,这个时候执行unparkSuccessor()
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
复制代码
这个时候执行unparkSuccessor
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
复制代码
现将节点状态改为0 然后使用LockSupport精准唤醒节点的下一个结点 也就是第二个线程的节点,此时第二个线程被唤醒
此时state状态为0 进入第一个if逻辑抢锁成功,将自己设置为头节点 并清除和上一个头节点的引用,帮助垃圾回收
如此 循环往复 就是AQS实现ReentrantLock的基本原理了
本文着重介绍了ReentrantLock lock unlock方法,里面还有取消中断等逻辑,本文不做叙述,有机会再跟大家聊聊