核心概念
state 状态变量
通过volatile int state表示资源状态,子类通过CAS操作修改该值 例如:ReentrantLock:state表示锁的重入次数。 Semaphore:state表示可用许可数量。 CountDownLatch:state表示未完成的计数
同步队列
CAS state失败后,认为是竞争锁失败,包装成Node节点加入队列中
节点模式
- 共享模式,多个线程可以共享资源 (如Semaphore、CountDownLatch)
java
static final Node SHARED = new Node();
- 独占模式,只有一个线程获取资源(如ReentrantLock)
java
static final Node EXCLUSIVE = null;
Node 队列节点类分析
核心属性字段
java
//共享模式
static final Node SHARED = new Node();
//独占模式
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;
//节点状态
volatile int waitStatus;
//上一个节点
volatile Node prev;
//下一个节点
volatile Node next;
//关联线程
volatile Thread thread;
//条件队列的下一个等待节点
Node nextWaiter;
构造函数
arduino
//同步队列节点使用
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
//条件队列节点使用
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
ConditionObject 条件队列
ConditionObject 用于实现等待/通知机制(await()和signal()),类似于Object的wait/notify机制,但是更灵活,可以支持多个条件队列。
同步队列与条件队列的关系
- 同步队列用于管理竞争锁的线程节点,双向链表结构,通过prev和next构建双向链表
- 条件队列用于管理等待条件的线程节点,单链表结构,通过nextWaiter链接。
关键点:
- 每个 ConditionObject 实例对应一个独立的条件队列。
- 条件队列中的节点状态为 CONDITION(值为-2),表示线程正在等待条件触发。
- 当条件满足时(调用 signal()),节点从条件队列转移到同步队列,状态更新为 0,重新参与锁竞争。
属性字段
java
// 条件队列第一个节点
private transient Node firstWaiter;
//条件队列最后一个节点
private transient Node lastWaiter;
核心方法
signal()
java
public final void signal() {
//是否持有锁,因为此操作要先加锁成功
if (!isHeldExclusively())
throw new IllegalMonitorStateException
//条件队列首节点
Node first = firstWaiter;
if (first != null)
//唤醒
doSignal(first);
}
typescript
private void doSignal(Node first) {
do {
//当前节点是最后一个节点
if ( (firstWaiter = first.nextWaiter) == null
//置空
lastWaiter = null;
//头节点从队列中移除
first.nextWaiter = null;
}
//迁移失败并且新的首节点不为空,继续尝试迁移新的首节点
while (
//迁移到同步队列
!transferForSignal(first) &&
//新的首节点不为空
(first = firstWaiter) != null);
}
scss
final boolean transferForSignal(Node node) {
/*
* If cannot change waitStatus, the node has been cancelled.
*/
//CAS设置这个节点的waitStatus 设置为0,失败返回
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
/*
* Splice onto queue and try to set waitStatus of predecessor to
* indicate that thread is (probably) waiting. If cancelled or
* attempt to set waitStatus fails, wake up to resync (in which
* case the waitStatus can be transiently and harmlessly wrong).
*/
//入队到同步队列
Node p = enq(node);
int ws = p.waitStatus;
//如果入队节点的状态是CANCELLED状态,或者CAS设置状态为SIGNAL
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
//唤醒线程
LockSupport.unpark(node.thread);
return true;
}
java
private Node enq(final Node node) {
for (;;) {
//尾节点
Node t = tail;
//尾节点是空的,说明同步队列为空
if (t == null) { // Must initialize
//无参构造一个Node CAS设置到头节点
if (compareAndSetHead(new Node()))
//尾节点也指向它
tail = head;
}
//队列不为空
else {
//入队node的prev指针指向队列的尾节点
node.prev = t;
//CAS 把入队node设置成新的尾节点
if (compareAndSetTail(t, node)) {
//队列尾节点next指针指向入队node
t.next = node;
return t;
}
}
}
}
signal()逻辑总结
- 校验是否获取锁,signal在加锁后操作,不然抛出异常;
- 从条件队列的首节点开始,将其状态设置为0,成功的话转移到同步队列队尾并返回其前驱节点;
- 失败的话并且还有后继节点,重复第二步逻辑;
- 第二步成功,检查第二步返回前驱节点的状态,如果是CANCELA状态或者CAS设置SIGNAL状态失败,说明前驱节点有问题,直接唤醒当前节点以确保前驱节点的问题造成当前节点不能被唤醒
await()
scss
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//添加条件队列节点到条件队列中
Node node = addConditionWaiter();
//唤醒同步队列节点线程
int savedState = fullyRelease(node);
int interruptMode = 0;
//是否在同步队列中
while (!isOnSyncQueue(node)) {
//不在的话阻塞
//判断节点是否在同步队列中,一开始肯定不在同步队列中,一直阻塞着,直到被唤醒加入到同步队列或中断
LockSupport.park(this);
//interruptMode不等于0退出循环,表示放入同步队列成功
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
//重新获取锁,检查中断状态
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
//清理CANCLE节点
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
//不同中断模式去处理
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
ini
private Node addConditionWaiter() {
//尾节点
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
//尾节点不是CONDITION状态
if (t != null && t.waitStatus != Node.CONDITION) {
//释放条件队列不是CONDITION状态的所有节点
unlinkCancelledWaiters();
//更新尾节点
t = lastWaiter;
}
//构造一个新的节点状态为CONDITION
Node node = new Node(Thread.currentThread(), Node.CONDITION);
//队列是空的
if (t == null)
//头指针指向此节点
firstWaiter = node;
//队列不为空
else
//原队列尾节点指向新节点
t.nextWaiter = node;
//尾指针指向此节点
lastWaiter = node;
return node;
}
ini
private void unlinkCancelledWaiters() {
Node t = firstWaiter;
//上一个有效节点
//单链表清除无效节点需要一个指针去处理
Node trail = null;
while (t != null) {
Node next = t.nextWaiter;
//满足清除条件
if (t.waitStatus != Node.CONDITION) {
//断开和下个节点的链接
t.nextWaiter = null;
if (trail == null)
//头指针指向第一个有效节点,就不用动了
firstWaiter = next;
else
//跳过当前无效节点
trail.nextWaiter = next;
if (next == null)
//尾指针指向尾节点
lastWaiter = trail;
}
else
//更新有效节点
trail = t;
//更新下一个指针
t = next;
}
}
ini
final int fullyRelease(Node node) {
boolean failed = true;
try {
//返回同步状态
int savedState = getState();
//释放
if (release(savedState)) {
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
kotlin
final boolean isOnSyncQueue(Node node) {
//状态是CONDITION或者上个节点为空,不在同步队列中
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
//有后继节点一定在同步队列中
if (node.next != null) // If has successor, it must be on queue
return true;
/*
* node.prev can be non-null, but not yet on queue because
* the CAS to place it on queue can fail. So we have to
* traverse from tail to make sure it actually made it. It
* will always be near the tail in calls to this method, and
* unless the CAS failed (which is unlikely), it will be
* there, so we hardly ever traverse much.
*/
//在同步队列中查找
return findNodeFromTail(node);
arduino
private int checkInterruptWhileWaiting(Node node) {
return Thread.interrupted() ?
(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
0;
}
java
final boolean transferAfterCancelledWait(Node node) {
//CAS节点状态为0
if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
//放入同步队列
enq(node);
return true;
}
/*
* If we lost out to a signal(), then we can't proceed
* until it finishes its enq(). Cancelling during an
* incomplete transfer is both rare and transient, so just
* spin.
*/
//不在同步队列中不能唤醒,自旋等待加入同步队列
while (!isOnSyncQueue(node))
Thread.yield();
return false;
}
await逻辑总结:
- 构建条件队列节点加入到条件队列,并把条件队列中的不是CONDITION状态的节点清除掉;
- 完全释放锁,如果同步队列的首节点是有效节点,唤醒其后继节点;
- 判断节点是否在同步队列中,一开始肯定不在同步队列中,一直阻塞着,直到被唤醒加入到同步队列或中断;
- 如果等待期间被中断,根据中断时机设置中断模式();
- 唤醒后重新获取锁;
- 清理无效节点;
- 根据中断模式处理,THROW_IE抛出中断异常,REINTERRUPT 进行中断
AQS 核心方法分析
acquire(int arg)
独占模式下获取锁
scss
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
arduino
protected boolean tryAcquire(int arg) {
//子类实现
throw new UnsupportedOperationException();
}
ini
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节点的前驱指针和尾节点连接
node.prev = pred;
//把新加入的节点CAS设置为尾节点
if (compareAndSetTail(pred, node)) {
//之前尾节点的next指针和新加入节点链接,完成新节点加入到同步队列中
pred.next = node;
return node;
}
}
//如果同步队列是空的执行enq方法逻辑
enq(node);
return node;
}
java
private Node enq(final Node node) {
for (;;) {
//尾节点
Node t = tail;
//尾节点是空的,说明同步队列为空
if (t == null) { // Must initialize
//无参构造一个Node CAS设置到头节点
if (compareAndSetHead(new Node()))
//尾节点也指向它
tail = head;
}
//队列不为空
else {
//入队node的prev指针指向队列的尾节点
node.prev = t;
//CAS 把入队node设置成新的尾节点
if (compareAndSetTail(t, node)) {
//队列尾节点next指针指向入队node
t.next = node;
return t;
}
}
}
}
ini
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;
}
//阻塞并中断
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
arduino
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表示CANCLE状态
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
//找到前驱节点不是CANCLE的
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.
*/
//更新前驱节点为SIGNAL
//
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
java
private void cancelAcquire(Node node) {
// Ignore if node doesn't exist
if (node == null)
return;
//清空线程
node.thread = null;
// Skip cancelled predecessors
//跳过已取消的前驱节点,找到不为CANCLE的节点
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.
//记录前驱节点的next指针
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;
//不是尾节点,把前驱节点的next指针指向当前节点的next指针,也就把当前节点从链表中断开了
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 {
//唤醒后继节点
unparkSuccessor(node);
}
//自己本身的next指针指向自己,就和别人没关系了,GC root不可达,帮助垃圾回收
node.next = node; // help GC
}
}
acquire 的流程总结:
- 尝试直接获取锁;
- 获取锁失败,加入到同步队列;
- 循环尝试获取锁。 如果前驱节点不是头节点或者获取锁失败,判断前驱节点状态是否是SGNAL,是的话放心阻塞;
- 判断前驱节点状态如果是CANCELLED状态,往上找不是CACLE状态的节点,继续第三步;
- 前驱节点状态不是CANCELLED状态,设置前驱节点为SIGNAL状态继续第三步;
- 直到第三步满足前驱节点是头节点并且获取锁成功,移除头节点返回中断状态;
- 如果第六步是true进行中断操作;
- 在执行acquireQueued方法逻辑中出现异常(线程被中断抛出中断异常、其他异常等)执行cancelAcquire方法取消这个有问题的节点
release(int arg)
独占模式下释放锁
java
public final boolean release(int arg) {
//释放锁
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
//唤醒后继节点
unparkSuccessor(h);
return true;
}
return false;
}
arduino
protected boolean tryRelease(int arg) {
//子类实现
throw new UnsupportedOperationException();
}
java
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;
//小于0设置状态为0
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;
//下个节点为空或者状态大于0
if (s == null || s.waitStatus > 0) {
//置空
s = null;
//从队尾一直找,找到状态小于等于0的节点
//这段代码是addWaiter(Node mode)方法入队时候通过CAS (compareAndSetTail(pred, node))
//保证了
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
//唤醒线程
LockSupport.unpark(s.thread);
}
unparkSuccessor为什么从队尾开始找?
从上面两张截图红框位置可以看到prev 指针的设置在 CAS 更新尾指针前已经完成,而next指针在并发操作的时候可能还是空的,所以用pre去操作更安全,可以得到正确的结果
release(int arg)方法总结:
- 子类实现tryRelease(arg)成功;
- 唤醒后继节点。首节点的下一个节点不为空并且状态小于等于0,说明是有效节点直接唤醒;否则从队尾一直找,找到状态小于等于0的节点并唤醒
acquireShared(int arg)
共享模式获取锁
arduino
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
arduino
protected int tryAcquireShared(int arg) {
//子类实现
throw new UnsupportedOperationException();
}
ini
private void doAcquireShared(int arg) {
//添加共享模式节点
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//前驱节点
final Node p = node.predecessor();
//前驱节点是头节点
if (p == head) {
//获取锁
int r = tryAcquireShared(arg);
if (r >= 0) {
//
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
ini
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
//设置新节点为头节点
setHead(node);
//propagate > 0任有剩余资源
//h==null表示原头节点被移除
//h.waitStatus < 0 表示需要唤醒后续节点
//通过二次获取 head 并检查其状态,确保判断基于最新队列状态,防止遗漏
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
//传播唤醒
doReleaseShared();
}
}
csharp
private void doReleaseShared() {
for (;;) {
Node h = head;
//头节点不为空并且头节点不是尾节点(队列不止头节点)
if (h != null && h != tail) {
int ws = h.waitStatus;
//状态是SIGNAL
if (ws == Node.SIGNAL) {
//设置状态为0
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
//失败继续
continue; // loop to recheck cases
//成功唤醒后继节点
unparkSuccessor(h);
}
//状态为0设置waitStatus为PROPAGATE
//这里设置为PROPAGATE表示后续线程继续做传播唤醒工作
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
//成功继续
continue; // loop on failed CAS
}
//头节点没有变化,说明当前线程已经完成了唤醒操作,退出循环
//如果有变化,说有其他线程修改了头节点,有新的头节点诞生,继续循环处理
if (h == head) // loop if head changed
break;
}
}
acquireShared(int arg)方法流程总结:
- (tryAcquireShared(arg) < 0)获取锁失败,添加共享节点到同步队列;
- 循环尝试获取锁。 如果前驱节点不是头节点,判断前驱节点状态是否是SGNAL,是的话放心阻塞;
- 判断前驱节点状态如果是CANCELLED状态,往上找不是CANCELLED状态的节点,继续第二步;
- 前驱节点状态不是CANCELLED状态,设置前驱节点为SIGNAL状态,继续第二步;
- 直到第二步满足前驱节点是头节点并且获取锁成功,设置当前节点为头节点,并唤醒后继节点,如果需要中断进行中断,退出循环
releaseShared(int arg)
共享模式释放锁
arduino
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
arduino
protected boolean tryReleaseShared(int arg) {
//子类实现
throw new UnsupportedOperationException();
}
csharp
private void doReleaseShared() {
for (;;) {
Node h = head;
//头节点不为空并且头节点不是尾节点(队列不止头节点)
if (h != null && h != tail) {
int ws = h.waitStatus;
//状态是SIGNAL
if (ws == Node.SIGNAL) {
//设置状态为0
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
//失败继续
continue; // loop to recheck cases
//成功唤醒后继节点
unparkSuccessor(h);
}
//状态为0设置waitStatus为PROPAGATE
//这里设置为PROPAGATE表示后续线程继续做传播唤醒工作
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
//成功继续
continue; // loop on failed CAS
}
//头节点没有变化,说明当前线程已经完成了唤醒操作,退出循环
//如果有变化,说有其他线程修改了头节点,有新的头节点诞生,继续循环处理
if (h == head) // loop if head changed
break;
}
}
releaseShared(int arg)方法流程总结:
- 尝试释放资源;
- 传播唤醒后续节点
总结
- 重点分析了ConditionObject 条件队列的signal()和await()方法, signalAll()、awaitNanos(long nanosTimeout)等方法逻辑类似,没有再进行分析
- 重点分析了AQS中的acquire(int arg), release(int arg), acquireShared(int arg), releaseShared(int arg)方法,其他的方法逻辑类似
- 理解这些逻辑,再去看ReentrantLock、Semaphore、CountDownLatch源码会简单不少,后续会分析这三个类的源码理解