前言
上一篇文章从ReentrantLock源码深入理解AQS我们通过分析ReentrantLock源码了解其实现原理,理解了AQS的思想,这次我们再以这种方式来解析Condition,我们的入口还是ReentrantLock类(别再解析我了,都被扒光了。。)
Condition简介
Condition其实是J.U.C下的一个接口,最主要的三个方法是await()、signal()、signalAll(),其功能和Object类的wait()、notify()、notifyAll()类似,根据JDK源码的注释大致翻译下每个方法功能:
await()
释放当前Condition对象关联的锁,当前线程会一直等待直到下列四种情况发生:
- 其他线程调用了此Condition对象的signal()方法,并且刚好选中当前线程进行唤醒
- 其他线程调用了此Condition对象的signalAll()方法
- 其他线程中断了当前线程(Thread#interrupt方法)
- 发生“虚假唤醒”(我还没太明白这个意思)
在以上所有情况下,在当前线程从await()方法返回之前,当前线程必须重新获取该Condition对象关联的锁。
根据上述说明,可以总结几个关键点:
1.Condition对象是和一个锁对象关联的
2.调用Condition.await(),一旦这个方法执行完成返回后,当前线程一定重新获取到了锁
signal()
唤醒一个等待的线程
signalAll()
唤醒所有等待的线程
从ReentrantLock看Condition
初始化
从上文可以知道,Condition对象一定是一个锁对象关联起来的,例如,当我们使用ReentrantLock时,如何获取一个Condition对象呢?答案是调用ReentrantLock.newCondition()方法。
public class ReentrantLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = 7373984872572414699L;
private final Sync sync;
public Condition newCondition() {
return sync.newCondition();
}
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
final ConditionObject newCondition() {
return new ConditionObject();
}
}
}
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer
implements java.io.Serializable {
public class ConditionObject implements Condition, java.io.Serializable {
private static final long serialVersionUID = 1173984872572414699L;
/** 条件队列中的第一个节点. */
private transient Node firstWaiter;
/** 条件队列中的最后节点. */
private transient Node lastWaiter;
/**
* Creates a new {@code ConditionObject} instance.
*/
public ConditionObject() { }
}
}
从源码可以看到,调用ReentrantLock.newCondition(),实际是调用其内部类Sync的一个实例属性的sync.newCondition(),这个方法是调用ConditionObject的无参构造方法,而ConditionObject是AQS类的一个内部类,它实现了Condition接口。所以,类继承和调用关系图如下:
Condition使用
我们先通过一个用例来看下Condition的使用:
public class MyLock extends AbstractQueuedSynchronizer {
static ReentrantLock lock = new ReentrantLock();
static Condition condition = lock.newCondition();
static volatile boolean flag = false;
public static class Thread1 implements Runnable{
@Override
public void run() {
lock.lock();
System.out.println("线程1获取到锁");
try{
System.out.println("线程1执行逻辑");
while (!flag){
System.out.println("条件不满足,线程1挂起");
condition.await();
System.out.println("线程1被唤醒");
}
System.out.println("条件满足,线程1继续执行");
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
System.out.println("线程1执行完毕");
}
}
}
public static class Thread2 implements Runnable{
@Override
public void run() {
try {
//先等待2s,保证线程1先获取锁
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.lock();
System.out.println("线程2获取到锁");
try{
System.out.println("线程2执行逻辑");
flag = true;
condition.signal();
//关注点:让线程2睡眠5s
Thread.sleep(5000);
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
System.out.println("线程2执行完毕");
}
}
}
public static void main(String[] args) {
new Thread(new Thread1()).start();
new Thread(new Thread2()).start();
}
}
执行结果:
线程1获取到锁
线程1执行逻辑
条件不满足,线程1挂起
线程2获取到锁
线程2执行逻辑
线程2执行完毕
线程1被唤醒
条件满足,线程1继续执行
线程1执行完毕
Process finished with exit code 0
执行逻辑很简单,线程1先获取到锁,线程2获取锁失败进入等待,线程1发现条件不满足后,调用await()进行挂起,释放锁,然后线程2获取锁成功,将标志位flag置为true后,将线程1唤醒,线程2执行完毕后释放锁,然后线程1重新获取锁,发现条件满足后跳出循环执行完毕,最后释放锁。
注意代码中有个关注点,线程2调用signal()方法后,进行了5s的休眠,为什么要这么做,是为了突出之前说的两点:
1.调用signal方法只是唤醒一个线程,仅仅是唤醒,但锁还在当前线程手中
2.调用await方法的线程,当此方法执行完毕返回后,当前线程一定重新获取到了锁
所以我们看到,当线程2调用signal()方法然后休眠后,控制台没有新的内容打印,线程1并没有继续执行,因为此时线程2并没有释放锁,所以线程1虽然被唤醒,但仍在await方法中,直到线程2执行完成后,线程1才打印出“线程1被唤醒”。
接下来我们看看await()方法的源码实现,准确说,是ConditionObject对await()的实现:
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer
implements java.io.Serializable {
public class ConditionObject implements Condition, java.io.Serializable {
private static final long serialVersionUID = 1173984872572414699L;
/** 条件队列中的第一个节点. */
private transient Node firstWaiter;
/** 条件队列中的最后节点. */
private transient Node lastWaiter;
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);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
//尝试获取锁,传入的state是之前保存的值,即保证重入次数和之前一致
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
//去除等待队列中状态不是Condition的节点
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
/**
* Adds a new waiter to wait queue.
* @return its new wait node
*/
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;
}
}
/**
* Invokes release with current state value; returns saved state.
* Cancels node and throws exception on failure.
* @param node the condition node for this wait
* @return previous sync state
*/
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;
}
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
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);
}
}
await方法首先是将当前线程封装成Node加入到等待队列中,创建Node对象时传入的waitStatus是Node.CONDITION,表示在等待队列中,在这里我们发现,AQS中有两个队列:同步队列(sync queue)和等待队列(condition queue,或者叫条件队列),同步队列就是我们上一篇文章从ReentrantLock源码深入理解AQS中提到的,当线程获取锁失败后进入的队列,两个队列中用的节点都是Node对象,根据Node.waitStatus来区分是在哪个队列中(对同一个对象,改变一个属性值就可以标识在不同的队列中,的确设计的很巧妙)。加入等待队列后,当前线程会释放锁,对于可重入锁,这里会重入次数保存,方便下次重新获取锁后保持重入次数与之前一致。然后判断当前线程是否在同步队列中,此时Node.waitStatus=Node.CONDITION,因此进入循环体中,当前线程挂起,等待被唤醒,后续流程我们等分析完signal()方法后再继续。
public final void signal() {
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);
}
/**
* Transfers a node from a condition queue onto sync queue.
* Returns true if successful.
* @param node the node
* @return true if successfully transferred (else the node was
* cancelled before signal)
*/
final boolean transferForSignal(Node node) {
/*
* 如果设置状态失败,说明当前节点已经是取消状态了
*/
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
//将当前节点入队到同步队列中
Node p = enq(node);
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
线程2调用signal()方法后,会获取等待队列中的头结点,将头结点的waitStatus从CONDITION设置为0,然后加入到同步队列中,并把同步队列中的这个节点的前一节点状态置为SIGNAL,表示它的下一个节点等待被唤醒,如果前一节点waitStatus=CACELLED或者设置为SIGNAL失败,就会直接唤醒当前线程。
signal()方法很简单,执行完后我们回到await()方法挂起的地方,被唤醒后由于waitStatus=0,所以isOnSyncQueue(node)返回true,跳出循环,然后调用acquireQueued方法尝试获取锁,acquireQueued之前我们已经分析过,如果当前节点不是同步队列的头结点(准确说同步队列的头结点是一个空节点,这里的头结点指的第一个等待被唤醒的节点),则又会挂起进行等待,所以这时其实线程1还在await方法中,因为它没有获取到锁,直到线程1在同步队列中排队等到了并获取到锁后,acquireQueued返回,然后await方法才执行完毕。
到此,Condition的await和signal分析完毕。
小结
从AQS的实现逻辑可以看出,当线程获取锁失败后,会进入同步队列中等待,而当获取锁的线程调用await方法后,会释放锁并进入等待队列中等待,当线程从等待队列中被唤醒后,会进入到同步队列中继续等待,直到成为同步队列中的头结点后,才能获取到锁继续执行。
Last but not least
码字不易,给个点赞在走吧,与君共勉~~