持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第21天,点击查看活动详情
ReentrantLock虽然实现了互斥,但是如何实现进程间的相互通信呢?
这就需要借助Condition来实现。就像synchronized实现互斥,同时配合notify()和wait()方法来实现线程的通信一样。Condition必须和Lock配合使用,其提供await()方法和signal()方法,作用类似wait()和notify(),用来实现线程间的通信。
一、Condition与Object的关系
- Condition类的 awiat() 方法和Object类的 wait() 方法等效;
- Condition类的 signal() 方法和Object类的 notify() 方法等效;
- Condition类的 signalAll() 方法和Object类的 notifyAll() 方法等效;
- ReentrantLock类可以唤醒指定条件的线程,而object的唤醒是随机的。
二、与wait/notify区别
- Condition能够支持不响应中断,而通过使用Object方式不支;
- Condition能够支持多个等待队列(new 多个Condition对象),而Object方式只能支持一个;
- Condition能够支持超时时间的设置,而Object不支持。
三、Condition实现
创建一个ReentrantLock对象之后,调用该对象的newCondition()方法创建一个Condition对象;底层为调用了Sync.newCondition()方法,创建了一个ConditionObject对象。
public Condition newCondition() {
return sync.newCondition();
}
final ConditionObject newCondition() {
return new ConditionObject();
}
ConditionObject对象内部维护了一个双向链表,和AQS类似,持有一个firstWaiter和一个lastWaiter。
public class ConditionObject implements Condition, java.io.Serializable {
private static final long serialVersionUID = 1173984872572414699L;
/** First node of condition queue. */
private transient Node firstWaiter;
/** Last node of condition queue. */
private transient Node lastWaiter;
//.....其他代码
}
condition是要和lock配合使用的,而lock的实现原理又依赖于AQS,所以AQS内部实现了ConditionObject。我们知道在锁机制的实现上,AQS内部维护了一个双向的同步队列,如果是独占式锁的话,所有获取锁失败的线程的尾插入到同步队列。condition内部也是使用相似的方式,内部维护了一个单向的 等待队列,所有调用condition.await方法的线程会加入到等待队列中,并且线程状态转换为等待状态。
ConditionObject中有两个成员变量:头节点firstWaiter 和 尾节点lastWaiter ,同步队列的成员Node 复用了实现同步队列的内部类Node。用nextWaiter保存了下一个等待节点
await()方法的实现:
public final void await() throws InterruptedException {
//正要执行await,被中断,抛出异常
if (Thread.interrupted())
throw new InterruptedException();
//加入Condition的等待队列中,此时只是Node加入到了队列,线程还未阻塞
Node node = addConditionWaiter();
//释放锁
int savedState = fullyRelease(node);
int interruptMode = 0;
//检测节点是否在AQS阻塞队列中,如果不在,则唤醒自己也无法争抢锁,继续睡眠
//此处为假设两个线程,A持有锁,A调用await(),B直接被阻塞,然后A调用await(),让出锁,
//同时node插入到条件等待队列,且自己不再同步队列中,所以睡眠
//此时会唤醒B,B获得锁,然后B调用signal(),将A的Node从条件阻塞队列加入到同步队列,
//然后调用await(),B进入原来A的状态。同时在fullyRelease()函数中唤醒阻塞的第一个线程
//A醒来后,进行循环判断,判断自己在AQS中,
while (!isOnSyncQueue(node)) {
LockSupport.park(this);//自己阻塞自己
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
//被signal()方法唤醒后,要重新尝试获取锁
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
//清理条件队列中所有状态不是CONDITION状态的节点
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
//addConditionWaiter(),总体逻辑就是将Node加入到条件等待队列的队尾
private Node addConditionWaiter() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
//创建一个新的Node
Node node = new Node(Node.CONDITION);
//如果队列为空,即队列为空时,将node插入到队列中,并且作为队列第一个节点
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
final int fullyRelease(Node node) {
try {
int savedState = getState();//获取当前的重入次数
if (release(savedState))
return savedState;
throw new IllegalMonitorStateException();
} catch (Throwable t) {
node.waitStatus = Node.CANCELLED;
throw t;
}
}
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) {
//状态为Condition,获取前驱节点为null,返回false
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
//后继节点不为null,肯定在CLH同步队列中,因为如果Node中pre和next变量标记AQS阻塞队列的前驱和后继
//nextWaiter记录条件等待队列上的后继,所以next只有在进入到AQS阻塞队列才会设置
if (node.next != null)
return true;
//其他情况,从AQS阻塞队列的队尾开始遍历,查看当前节点是否在阻塞队列中,没想到这是什么情形。。。
return findNodeFromTail(node);
}
signal()方法的实现:
public final void signal() {
if (!isHeldExclusively()) //判断当前线程是否获取了锁,如果无锁,则抛异常
throw new IllegalMonitorStateException();
Node first = firstWaiter;//得到条件阻塞队列的队头
if (first != null)
doSignal(first);//唤醒队头第一个,不同于AQS的阻塞队列
//从条件阻塞队列唤醒后,还需要加入到AQS队列阻塞中
}
//将队头节点移入到AQS阻塞队列中,并将该节点从条件队列中移除
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
//transferForSignal()将头节点移动到AQS阻塞队列
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
//
final boolean transferForSignal(Node node) {
//判断当前node是否为CONDITION状态,如果不是则直接返回
if (!node.compareAndSetWaitStatus(Node.CONDITION, 0))
return false;
//将P加入到AQS阻塞队列中
Node p = enq(node);
int ws = p.waitStatus;
if (ws > 0 || !p.compareAndSetWaitStatus(ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
signal()只是将线程从Condition的条件阻塞队列移动到了AQS的阻塞队列,此时线程并没有被唤醒,只有当持有锁的线程调用了await()方法或者unlock()方法后,才会唤醒AQS阻塞队列中被阻塞的线程。而如果不调用signal(),则阻塞在条件队列上的线程永远不可能有机会进入到AQS阻塞队列。