概述
我们知道synchronized通过使用Object.wait、Object.notify方法,可以实现条件等待、唤醒操作。
Lock怎么实现这些呢?就是通过Condition接口实现的。Condition接口的实现类ConditionObject是AbstractQueuedSynchronizer的内部类,AQS类是前两篇就分析的,但ConditionObject没有分析,说明欠的债迟早是要还的😂
先看demo的使用
private static void testUseCondition() throws InterruptedException {
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
Thread t0 = new Thread(() -> {
lock.lock();
System.out.println(currentThread().getName() + " 线程加锁运行");
try {
System.out.println(currentThread().getName() + " 线程进入等待,释放锁资源");
condition.await();
System.out.println(currentThread().getName() + " 线程被唤醒,继续运行");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
});
t0.start();
TimeUnit.SECONDS.sleep(2L);
Thread t1 = new Thread(() -> {
lock.lock();
System.out.println(currentThread().getName() + " 线程加锁运行");
System.out.println(currentThread().getName() + " 线程唤醒condition等待");
condition.signal();
try {
TimeUnit.SECONDS.sleep(1L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(currentThread().getName() + " 运行完成");
lock.unlock();
});
t1.start();
}
运行结果:
Thread-0 线程加锁运行
Thread-0 线程进入等待,释放锁资源
Thread-1 线程加锁运行
Thread-1 线程唤醒condition等待
Thread-1 运行完成
Thread-0 线程被唤醒,继续运行
代码依然可以在github中下载
简单分析结果:
t0线程调用condition.await后,释放锁资源进入等待状态。
t1线程做了唤醒操作,但锁没有释放,t0继续等待
当t1线程释放资源后,t0线程继续加锁运行。\
下面我们一起分析下ConditionObject源码:
ConditionObject的数据结构
public class ConditionObject implements Condition, java.io.Serializable {
private transient Node firstWaiter;
private transient Node lastWaiter;
...
}

await方法

public final void await() throws InterruptedException {
//响应中断的等待
if (Thread.interrupted())
throw new InterruptedException();
//创建等待节点,加入等待队列
Node node = addConditionWaiter();
//释放锁,并唤醒同步队列中的后继节点,将state值返回,暂存
int savedState = fullyRelease(node);
int interruptMode = 0;
//判断是否在同步队列中,不在同步队列,就挂起自己
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
//从挂起中唤醒以后请求同步队列,成功以后判断是否被中断
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
//将取消等待的节点移除等待队列
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
//根据中断模式的值进行相应的处理
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
- 判断是否被中断,是中断抛出InterruptedException
- 创建等待节点,加入到等待队列
- 释放锁,锁了多少次,全释放掉,唤醒后继节点,并暂存state值(因为锁是可重入的,每次重入state都会加一次和)
- 如果不在同步队列中,进行park,挂起自己。(要提醒自己进行LockSupport.park(this)后,代码就不再往下执行了)
- 从挂起中被唤醒后(挂起被唤醒有两种情况:LockSupport.unpark即其他线程调用了signal方法或者其他线程调用中断方法),判断中断状态,为0跳出循环,进入下一步,不为0,说明是signal唤醒的,再次判断等待节点是否在同步队列中了。
- 成功唤醒后,节点进行请求队列操作(acquireQueued)
- 如果有后继等待节点,将取消状态的等待节点移除
- 根据中断模式的值进行中断或者抛出InterruptedException异常处理
addConditionWaiter方法
private Node addConditionWaiter() {
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
- 移除取消等待的节点
- 创建一个等待节点
- 如果等待队列为空,将该节点设为等待队列的头节点和尾节点
- 如果等待不为空,将该节点设为尾节点,并将之前的尾节点的nextWaiter指向该节点,如图5.11所示
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;
}
}
- 得到该线程锁定的state值,暂存起来供唤醒以后恢复
- 释放锁资源
- 如果异常退出将node的waitStatus设置为取消状态
isOnSyncQueue方法
final boolean isOnSyncQueue(Node node) {
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
if (node.next != null) // If has successor, it must be on queue
return true;
return findNodeFromTail(node);
}
private boolean findNodeFromTail(Node node) {
Node t = tail;
for (;;) {
if (t == node)
return true;
if (t == null)
return false;
t = t.prev;
}
}
- 判断是否在等待队列中,在就返回false
- 如果node.prev!=null && node.next==null 肯定在同步队列中,返回true
- 否则在同步队列中找这个节点,找到就是true否则返回false
当进入同步等待挂起后,就等着别人唤醒了,下面我们一起看一下signal方法
signal方法
public final void signal() {
//没有占用锁则抛出IllegalMonitorStateException异常
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
//唤醒等待队列的头节点
if (first != null)
doSignal(first);
}
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
- 将头节点设为后继节点,如果没后继节点了,将尾节点也设为null
- 将节点移到同步队列中(当节点状态被设置取消是,transferForSignal方法返回false)
- 如果first节点被取消,将新头节点进行唤醒
final boolean transferForSignal(Node node) {
//如果CAS更新失败,说明Node节点已经不是CONDITION状态,说明已被取消
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
//加入同步队列 并返回同步队列中的前继节点
Node p = enq(node);
int ws = p.waitStatus;
//如果前继节点被取消或者CAS设置state为SIGNAL时失败了
//唤起当前节点的线程,让它重新同步
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
我们signal和await串起来看
- 当await方法执行完这个LockSupport.park(this),就挂起了
- 当调用signal方法,执行enq(node)后,节点重新进入同步队列,并返回前继节点
- 如果前继节点是取消状态,或者前继节点的状态设置不成功,将节点唤起,唤起后后进入acquireQueued(node, savedState)方法
3.1. 这个方法会再次尝试加锁,不成功会再次让自己挂起😂 - 当然如果ws<=0 && compareAndSetWaitStatus(p, ws, Node.SIGNAL)设置成功,那线程就安心休息就好了,因为前继节点已经设置了SIGNAL状态,它释放锁以后会叫醒你的