AbstractQueuedSynchronizer 理解
AQS 成员变量
/**
* Head of the wait queue, lazily initialized. Except for
* initialization, it is modified only via method setHead. Note:
* If head exists, its waitStatus is guaranteed not to be
* CANCELLED.
*/
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.
* 同步状态,用于展示当前临界资源的获锁情况
* 0代表没有被占用,大于0代表有线程持有当前锁
* 这个值可以大于1,是因为锁可以重入,每次重入都加上 1
*/
private volatile int state;
线程获取锁的两种模式:
通过修改State字段表示的同步状态来实现多线程的独占模式和共享模式
- 独占模式:一旦被占用,其他线程都不能占用
- 共享模式:一旦被占用,其他共享模式下的线程才能占用
头节点和尾节点的作用:
当一个线程在某一时刻没有获取到共享资源,它可以选择进行排队。
这个队列是一个FIFO先进先出的双向链表,head 和 tail 分别表示队列的头和尾
+------+ prev +-----+ +-----+
head | | <---- | | <---- | | tail
+------+ +-----+ +-----+
Node 成员变量
static final class Node {
/** Marker to indicate a node is waiting in shared mode */
//表示线程以共享的模式等待锁
static final Node SHARED = new Node();
/** Marker to indicate a node is waiting in exclusive mode */
//表示线程正在以独占的方式等待锁
static final Node EXCLUSIVE = null;
/** waitStatus value to indicate thread has cancelled */
//表示线程获取锁的请求已经取消了
static final int CANCELLED = 1;
/** waitStatus value to indicate successor's thread needs unparking */
//表示线程已经准备好了,就等资源释放了
static final int SIGNAL = -1;
/** waitStatus value to indicate thread is waiting on condition */
//表示节点在等待队列中,节点线程等待唤醒
static final int CONDITION = -2;
/**
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate
*/
//当前线程处在SHARED情况下,该字段才会使用
static final int PROPAGATE = -3;
volatile int waitStatus; //当前节点在队列中的状态
volatile Node prev; //前指针
volatile Node next; //后指针
volatile Thread thread; //处于该节点的线程对象
//...
}
独占模式
tryAcquire
java.util.concurrent.locks.AbstractQueuedSynchronizer#tryAcquire
/**
* 尝试以独占模式获取锁
*/
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
acquire
java.util.concurrent.locks.AbstractQueuedSynchronizer#acquire
/**
* 以独占模式获取,忽略中断
* tryAcquire:如果tryAcquire获取锁,则直接跳出判断条件,不再执行后续操作
* 如果tryAcquire没有获取锁,就继续执行acquireQueued(node,arg)方法,进行排队等待锁
*/
@ReservedStackAccess
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
/**
* Convenience method to interrupt current thread.
* 如果acquireQueued为True,就会执行selfInterrupt方法
*/
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
java.util.concurrent.locks.AbstractQueuedSynchronizer#addWaiter
/**
* 将当前线程封装成一个Node加入等待队列,返回值为当前Node
*/
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; //获取尾节点,如果不为空,将其作为当前节点的前置节点,并通过CAS操作将当前节点置为尾节点
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node; //将前置节点的next指针指向已经成为尾节点的当前节点
return node;
}
}
enq(node); //当前尾节点为空或者第一次尝试CAS操作失败,则进入完整的入队方法
return node;
}
/**
* 将节点插入队列,必要时进行初始化
*/
private Node enq(final Node node) {
for (;;) { //自旋通过CAS将当前节点插入,直到入队成功为止
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;
}
}
}
}
将当前Node加入等待队列之后,就需要尝试让队列动起来,因为这是一个先入先出的队列。需要让队伍不断的向前推进,让队伍后面的节点更快获取锁。
java.util.concurrent.locks.AbstractQueuedSynchronizer#acquireQueued
/**
* 把放入队列中的线程不断去获取,直到获取成功或者不再需要获取(中断)
*/
@ReservedStackAccess
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;
}
//p为头节点并且没有获取到锁或者p不是头节点,判断当前node是否需要阻塞,防止无限循环浪费资源
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
//将Node的waitStatus置为CANCEL,以及其他的一些清理工作
cancelAcquire(node);
}
}
/**
* 判断是否需要挂起当前线程(通过前置节点的waitStatus)
* 如果返回true,则代表当前节点需要被挂起
*/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* 如果前置节点状态为SIGNAL,说明前置节点也在等待拿锁
* 说明当前节点可以挂起休息,直接返回true
*/
return true;
if (ws > 0) {
/*
* 如果前置节点状态大于0,那么状态只可能是CANCEL,所以可以将节点从队列中删除
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0); //删除前面所有waitStatus>0的Node
pred.next = node;
} else {
/*
* 通过CAS将前置节点的waitStatus置换为SIGNAL
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
/**
* Convenience method to park and then check if interrupted
*
* @return {@code true} if interrupted
*/
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this); //将当前线程挂起
return Thread.interrupted(); //返回当前线程的中断状态并清除
}
关于线程中断相关:https://www.zhihu.com/question/41048032
tryRelease
java.util.concurrent.locks.AbstractQueuedSynchronizer#tryRelease
/**
* 以独占模式释放锁
*/
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
release
java.util.concurrent.locks.AbstractQueuedSynchronizer#release
/**
* 独占模式释放锁
* tryRelease:如果tryAcquire返回true,说明锁没有被任何线程持有
*/
@ReservedStackAccess
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0) //如果头节点不为空并且waitStatus不等于0
unparkSuccessor(h);
return true;
}
return false;
}
/**
* 唤醒后续节点(如果存在)
*
* @param node the node
*/
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0); //将头节点的waitStatus置为0默认值,这样才不会影响其他函数的判断
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
//从尾节点开始搜索,找到除head节点之外一个最靠前并且waitStatus小于0的节点
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
//对其进行取消中断操作,唤醒该节点起来干活
LockSupport.unpark(s.thread);
}
为什么唤醒节点的操作不同头开始往后搜索,而是要从后往前搜索?
参考文章
https://www.bilibili.com/video/BV12K411G7Fg/?spm_id_from=333.788&vd_source=1cb3897146f4b6f7475f43d795c1f9ba
https://tech.meituan.com/2019/12/05/aqs-theory-and-apply.html
https://javadoop.com/post/AbstractQueuedSynchronizer