写在前面
Condition源码学习AQS
demo
Condition必须依赖lock,需要在锁下创建condition。类似wait、notify或notifyAll 与 synchronized 的关系
咱们根据这个demo来解读condition代码
public static void main(String[] args) {
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
new Thread(()->{
lock.lock();
System.out.println("线程1抢占到了这把锁!");
try {
Thread.sleep(4000);
condition.await();
System.out.println("线程1继续执行!");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.unlock();
},"线程1").start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
lock.lock();
System.out.println("线程2抢占到了这把锁!");
try {
condition.signalAll();
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我唤醒了其他线程!");
lock.unlock();
},"线程2").start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
lock.lock();
System.out.println("线程3抢占到了这把锁!");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.unlock();
},"线程3").start();
}
执行结果
线程1抢占到了这把锁!
线程2抢占到了这把锁!
我唤醒了其他线程!
线程3抢占到了这把锁!
线程1继续执行!
lock.newCondition()
//默认创建ConditionObject
final ConditionObject newCondition() {
return new ConditionObject();
}
condition.await();
public final void await() throws InterruptedException {
//判断线程是否中断过
if (Thread.interrupted())
throw new InterruptedException();
//将当前线程加入到条件队列
Node node = addConditionWaiter();
//释放clh队列的锁
int savedState = fullyRelease(node);
int interruptMode = 0;
//是否在条件队列中
while (!isOnSyncQueue(node)) {
//阻塞当前线程
//什么是否被唤醒呢,此时已经释放锁
//当执行unlock时会将队列中的线程按照队列顺序执行
//当当前节点的前置节点执行unlock时会唤醒这个中断的线程
LockSupport.park(this);
//这里被唤醒可能是正常的signal操作也可能是中断
//如果线程中断过直接抛出异常
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
//加入到条件队列后,尝试获取锁
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
//走到这里说明已经成功获取到了独占锁,接下来就做些收尾工作
//删除条件队列中被取消的节点
if (node.nextWaiter != null)
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
将当前节点加入到条件队列
private Node addConditionWaiter() {
Node t = lastWaiter;
if (t != null && t.waitStatus != Node.CONDITION) {
//取消链表中不是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;
}
- 条件队列与clh队列不同,clh队列是一个双向链表,此处是一个单向链表
- clh队列有一个默认的head节点,条件队列是没有的,每个节点都是业务节点
- 执行此段代码只会有一个线程执行,因为执行此段代码前已经加完锁了,此处执行就是获取到锁的线程
fullyRelease 方法
唤醒条件队列
final int fullyRelease(Node node) {
boolean failed = true;
try {
//获取加锁状态
int savedState = getState();
//释放锁,前一节我们已经分析过代码
//就是将head的下一个节点进行唤醒
if (release(savedState)) {
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
isOnSyncQueue
- 用于判断是否在阻塞队列中(就是clh队列)
- 当前一步唤醒阻塞队列时,有可能有的线程已经执行了singleAll方法将条件队列的此节点加入到阻塞队列中
- 此方法就是判断如果没有将条件队列的此节点加入到阻塞队列时,此时需要阻塞住当前线程
- 如果加入到阻塞队列,尝试获取锁,并在阻塞队列中进行排队等待
condition.signalAll();
/**
* 唤醒所有条件队列的节点转移到同步队列当中
*/
public final void signalAll() {
//用于判断当前线程是否是获取到锁的线程,只有获取到锁的线程执行
//await 释放锁 signal 不会示释放锁 同 wait 与 notify
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//获取到条件的头 就是第一个节点
Node first = firstWaiter;
if (first != null)
//执行
doSignalAll(first);
}
private void doSignalAll(Node first) {
//将条件队列链表置为空
lastWaiter = firstWaiter = null;
do {
//先获取下第二个node节点
Node next = first.nextWaiter;
//将第一个节点抽取出来
first.nextWaiter = null;
//加入到条件队列
transferForSignal(first);
//第二个节点赋值到第一个节点
first = next;
} while (first != null);
}
- 代码中部分说明已经注释
- 循环执行将条件队列中所有等待的线程全部都加入到阻塞队列中
- 在阻塞队列中根据重入锁逻辑等待获取锁
transferForSignal
final boolean transferForSignal(Node node) {
/*
* 修改节点信号量状态为0,失败直接返回false
*/
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
/*
* 加入同步队列尾部当中,返回前驱节点
*/
Node p = enq(node);
int ws = p.waitStatus;
//前驱节点不可用 或者 修改信号量状态失败
//如果前驱节点不等于-1说明不会唤醒后面节点 这不是我们想要的 此时我们唤醒他
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread); //唤醒当前节点
return true;
}
加入到条件队列后,就跟阻塞队列其余线程一样,等待前驱节点的唤醒
我们demo的大致流程为