- AQS独占锁是很多并发包的基础,像读写锁,CountDownLatch都是基于AQS实现的,搞懂其原理对我们学习java并发包会有很好的作用。
- 先来看锁的几种状态
volatile int waitStatus; //锁状态
//以下几种状态代表锁的具体值
static final Node EXCLUSIVE = null;//代表独占锁模式
static final int CANCELLED = 1;//节点被取消
static final int SIGNAL = -1;//代表后续节点可以被唤醒
static final int CONDITION = -2;//指定条件下传播
static final int PROPAGATE = -3; //当前节点无条件向后传播
- 独占锁获取锁过程:
acquire(int arg)方法是我们获取独占锁的入口,看方法注释我们可以知道,该方法是获取独占锁的模式,如果成功的话,执行当前线程,否则的话会将当先线程放置于一个队列中,tryAcquire(arg)方法是我们是具体实现类实现获取锁的的方式,如果返回false也就是当前线程获取锁失败,则会执行*acquireQueued()*将当前线程执行加入队列
/**
* Acquires in exclusive mode, ignoring interrupts. Implemented
* by invoking at least once {@link #tryAcquire},
* returning on success. Otherwise the thread is queued, possibly
* repeatedly blocking and unblocking, invoking {@link
* #tryAcquire} until success. This method can be used
* to implement method {@link Lock#lock}.
*
* @param arg the acquire argument. This value is conveyed to
* {@link #tryAcquire} but is otherwise uninterpreted and
* can represent anything you like.
*/
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
获取锁失败后将当前线程加入队列:
/**
* Creates and enqueues node for current thread and given mode.
*
* @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
* @return the new 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;
//快速入队操作,如果tail存在直接将当前节点插入
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//完整的入队操作
enq(node);
return node;
}
完整的入队操作:
/**
* Inserts node into queue, initializing if necessary. See picture above.
* @param node the node to insert
* @return node's predecessor
*/
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;
}
}
}
}
当我们将之前步骤的线程加入队列后,就需要执行唤醒,或者继续循环的操作,以下是具体的实现
/**
* Acquires in exclusive uninterruptible mode for thread already in
* queue. Used by condition wait methods as well as acquire.
*
* @param node the node
* @param arg the acquire argument
* @return {@code true} if interrupted while waiting
*/
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//获取当前节点的前置节点
final Node p = node.predecessor();
//若前驱节点为头节点,并且已经获取锁,也就是tryAcquire(arg)为true,则将当前节点设置为头节点,退出循环
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//如果获取锁失败的情况,将挂起当前线程,并且执行中断操作,中断成功的话更新 interrupted = true;
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
获取锁失败后的处理:
/**
* Checks and updates status for a node that failed to acquire.
* Returns true if thread should block. This is the main signal
* control in all acquire loops. Requires that pred == node.prev.
*
* @param pred node's predecessor holding status
* @param node the node
* @return {@code true} if thread should block
*/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
//获取前置节点的状态为SIGNAL,也就是当前节点可以被安全挂起。
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
//如果前置节点>0也就一种情况,就是该节点的状态为取消状态
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
//向前替换当前节点的前置节点,直到满足当前节点的前置节点的状态不大于0
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
//否则的话,将pred的waitStatus 状态为 0 或者为 PROPAGATE.更新为SIGNAL,在挂起之前再试试能不能拿到锁。拿不到的话下次进来直接返回true
//也是与我们后续处理状态为0的操作做铺垫--(重点!)
} 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;
}
线程挂起,中断操作:
/**
* Convenience method to park and then check if interrupted
*
* @return {@code true} if interrupted
*/
private final boolean parkAndCheckInterrupt() {
//挂起线程
LockSupport.park(this);
//要是被中断,返回中断标志
return Thread.interrupted();
}
最后在finally块中对获取失败后的一些措施:
/**
* Cancels an ongoing attempt to acquire.
*
* @param node the node
*/
private void cancelAcquire(Node node) {
// 如果当前节点为空直接返回
if (node == null)
return;
//清空当前节点的线程
node.thread = null;
// 跳过无效的节点,也就是pred.waitStatus > 0的节点
Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
// predNext is the apparent node to unsplice. CASes below will
// fail if not, in which case, we lost race vs another cancel
// or signal, so no further action is necessary.
Node predNext = pred.next;
// Can use unconditional write instead of CAS here.
// After this atomic step, other Nodes can skip past us.
// Before, we are free of interference from other threads.
//将节点状态设置为取消
node.waitStatus = Node.CANCELLED;
// If we are the tail, remove ourselves.
//当前节点是尾节点的话直接删除
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
//如果当前节点的存在后续的节点需要唤醒(也就是当前节点不是尾节点),我们将唤醒其后续节点。
} else {
// If successor needs signal, try to set pred's next-link
// so it will get one. Otherwise wake it up to propagate.
int ws;
//如果当前节点的前驱节点不为头并且ws==SIGNAL,
//那么就将当前节点的前节点,与当前节点的后节点连在一起,相当去删除当前节点
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
//剩下的else也就是pred为头,或者ws==PROPAGATE或0
} else {
//唤醒node的后续节点
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
唤醒后续节点(也就是当前节点)操作(注释也有✌️标注):
/**
* Wakes up node's successor, if one exists.
*
* @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.
*/
//将当前节点状态更新为0,也就是释放;
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 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;
//后续节点为空或者是取消状态(s.waitStatus > 0),清空后续节点(s = null);
if (s == null || s.waitStatus > 0) {
s = null;
//从尾节点往前遍历,找到最近的有效节点(也就是上一步s == null || s.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);
}
- 再来看锁释放操作:
/**
* Releases in exclusive mode. Implemented by unblocking one or
* more threads if {@link #tryRelease} returns true.
* This method can be used to implement method {@link Lock#unlock}.
*
* @param arg the release argument. This value is conveyed to
* {@link #tryRelease} but is otherwise uninterpreted and
* can represent anything you like.
* @return the value returned from {@link #tryRelease}
*/
public final boolean release(int arg) {
//这个判断我们当前状态为获取锁的状态,不然没锁释放毛毛啊
if (tryRelease(arg)) {
Node h = head;
//特地说明下h.waitStatus != 0是因为我们释放锁后会将节点waitStatus更新为0,而且挂起锁的时候会将状态为0的更新为SIGNAL,所以不考虑这个条件
if (h != null && h.waitStatus != 0)
//从头节点开始释放操作
unparkSuccessor(h);
return true;
}
return false;
}
释放锁的具体操作:
/**
* Wakes up node's successor, if one exists.
*
* @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.
*/
//如果当前状态<0,也就是正在获取的锁中。则通过CAS将起更改为0(CAS规定了0为初始化状态),释放当前线程
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 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.
*/
//唤醒后继节点,但是要保证后继节点是有效的也就是 要满足 if (t.waitStatus <= 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);
}
最后总结下AQS锁的大体流程:在多线程的环境下,会将争夺共享资源的线程维持在一个FIFO的队列中进行自旋,如果某个线程获取到锁并且前驱节点为头节点,那么将当前节点设为头节点返回。而其他未获取锁的线程,会继续维持在队列,等待下次调用。