480
社区成员




Condition 是一个多线程协调通信的工具类,可以让某些线程一起等待某个条件(condition),只有满足条件时,线程才会被唤醒
调用了 await() 后,当前线程会释放锁并且等待,其他线程 signal() / signalall() 通知阻塞的线程并且释放锁,被唤醒的线程获得锁并且继续执行,最后释放锁。
public class ConditionWait implements Runnable {
private Lock lock;
private Condition condition;
public ConditionWait(Lock lock, Condition condition) {
this.lock = lock;
this.condition = condition;
}
@Override
public void run() {
try {
System.out.println("begin - ConditionDemoWait");
lock.lock();// 获得锁
condition.await();// 释放锁并等待,此时会将当前线程封装为Node放入到 condition 队列中
System.out.println("end - ConditionDemoWait");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public class ConditionSignal implements Runnable {
private Lock lock;
private Condition condition;
public ConditionSignal(Lock lock, Condition condition) {
this.lock = lock;
this.condition = condition;
}
@Override
public void run() {
try{
System.out.println("begin - ConditionDemoSignal");
lock.lock();
condition.signal();// 唤醒一个阻塞线程,从 condition 队列中将 Node 转换到 AQS 队列中
System.out.println("end - ConditionDemoSignal");
}finally {
lock.unlock();
}
}
}
public class ConditionMain {
public static void main(String[] args) {
Lock lock=new ReentrantLock(); // 重入锁
Condition condition=lock.newCondition();
new Thread(new ConditionWait(lock,condition)).start();
new Thread(new ConditionSignal(lock,condition)).start();
}
}
调用 Condition,需要获得 Lock 锁,所以也一定会存在有一个 AQS 队列
1、condition.await
使当前的线程进入等待队列并且释放锁,同时当前线程状态变为等待状态,从 await() 返回时,一定获取了 Condition 相关的锁
public final void await() throws InterruptedException {
if (Thread.interrupted()) throw new InterruptedException(); // 处理中断
Node node = addConditionWaiter(); // 创建一个节点,状态为 condition,结构依旧是链表
int savedState = fullyRelease(node); // 释放当前的锁,并且保留重入次数,唤醒 AQS 队列线程
int interruptMode = 0;
// 判断节点是否是在 AQS 队列中,第一次判断是 false,因为已经释放锁
while (!isOnSyncQueue(node)) {
LockSupport.park(this); // 挂起当前线程
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
// 线程被唤醒,尝试获取锁,acquireQueued 返回 false 就是获取到锁
// interruptMode != THROW_IE 表示这个线程没有成功的将 Node 入队,但是 signal 执行了 enq
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
// 如果 node 的下一个等待者不是 null,则清理 condition 队列的节点
unlinkCancelledWaiters();
if (interruptMode != 0) // 线程被中断
reportInterruptAfterWait(interruptMode);
}
2、addConditionWaiter
这个方法,用于将当前线程封装为 Node 并且添加到等待队列,这里的队列是单向链表
private Node addConditionWaiter() {
Node t = lastWaiter;
// 如果 lastWaiter != null,并且 waitStatus != CONDITION,将节点移除
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
// 构建一个 Node,waitStatus= CONDITION
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
3、fullyRelease
彻底的释放锁,如果是重入锁,只需要释放一次,就会把所有的重入次数清空
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;
}
}
4、isOnSyncQueue
在 condition 中,被唤醒的节点会被转移到 AQS 同步队列
判断当前节点是否在同步队列中,存在返回true
如果不在 AQS 同步队列,说明当前节点没有唤醒去争抢同步锁,所以需要把当前线程阻塞起来,直到其他的线程调用 signal 唤醒
如果在 AQS 同步队列,意味着它需要去竞争同步锁去获得执行程序执行权限
final boolean isOnSyncQueue(Node node) {
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
if (node.next != null)
return true;
return findNodeFromTail(node);
}
5、Condition.signal
public final void signal() {
// 判断当前线程是否获得了锁
if (!isHeldExclusively()) throw new IllegalMonitorStateException();
Node first = firstWaiter; // 获取 condition 队列中的第一个 Node
if (first != null) doSignal(first);
}
6、Condition.doSignal
对 condition 中的第一个节点,进行 transferForSignal 操作,将 node 从 condition 中转移到 AQS 队列,同时修改 AQS 尾部节点状态
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
7、AQS.transferForSignal
先是 CAS 修改了节点状态,如果成功,就将这个节 点放到 AQS 队列中,然后唤醒这个节点上的线程
final boolean transferForSignal(Node node) {
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
Node p = enq(node); // 调用 enq 将 node 加入到 AQS,返回加入后节点的上一个节点,也就是 tail
int ws = p.waitStatus;
// 如果上一个节点的状态被取消,或者尝试设置上一个节点的状态为 SIGNAL 失败
// SIGNAL 表示 next 节点需要被阻塞
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread); // 唤醒线程
// 如果 node 的 pre 节点已经是 SIGNAL 状态,那么阻塞的线程需要由 AQS 队列进行唤醒
return true;
}
阻塞:await()方法中,在线程释放锁资源之后,如果节点 不在 AQS 等待队列,则阻塞当前线程,如果在等待队列,则自旋等待尝试获取锁
释放:signal()后,节点会从 condition 队列移动到 AQS 等待队列,则进入正常锁的获取流程