这章来说一说Condition
Object中的notify、notifyAll没法唤醒一组特定的阻塞线程,只能唤醒全部中的一个或者全部(获取到锁的线程只有一个)
Condition它可以实现唤醒特定的一组线程中的一个线程,对于同一一个锁,它可以创建多个Condition,协调控制多组线程
Condition的具体实现ConditionObject
AQS中的内部类
ConditionObject重要属性
/**
* 条件队列的头节点指针
*/
private transient Node firstWaiter;
/**
* 条件队列的尾节点指针
*/
private transient Node lastWaiter;
Node与AQS源码解析一阻塞队列中的Node是一样的,这里就不解析了。
我们以ReentrantLock为例,看看ConditionObject是如何控制锁资源的获取
整体的数据结构
理清楚这个,再结合源码一起来理解分析,效果更佳。
ReentrantLock+Condition的典型应用
/**
* ReentrantLock+Condition的典型使用场景
* @author yangyangdai
*
*/
public class ConditionDemo {
//可重入锁的独占锁
private Lock lock = new ReentrantLock();
//不为满的条件 生产者阻塞 等待消费者消费数据之后唤醒
private Condition notFull = lock.newCondition();
//不为空的条件 消费者阻塞 等待生产者生产数据之后唤醒
private Condition notEmpty = lock.newCondition();
//对象数组
private Object[] items = new Object[5];
//生产数据的下标 消费数据的下标
private int putIndex,takeIndex;
//数据的数量
private AtomicInteger count = new AtomicInteger(0);
/**
* @Description: 生产数据
*/
public void put(Object o,int i) {
lock.lock();
try {
while(count.get() == items.length) {
notFull.await();
}
items[putIndex] = o;
if(++putIndex == items.length) {
putIndex = 0;
}
count.incrementAndGet();
System.out.println("线程 " + i + "-->生产数据 " + o);
notEmpty.signal();
} catch (Exception e) {
}finally {
lock.unlock();
}
}
/**
* @Description: 消费数据
*/
public Object take(int i) {
lock.lock();
try {
while (count.get() == 0) {
notEmpty.await();
}
Object o = items[takeIndex];
if(++takeIndex == items.length) {
takeIndex = 0;
}
count.decrementAndGet();
System.out.println("线程 " + i + "-->消费数据 " + o);
notFull.signal();
return o;
} catch (Exception e) {
}finally {
lock.unlock();
}
return null;
}
}
阻塞的对象数组实现:
ReentrantLock:
保证某一时间点,只有一个线程能生产或者消费数据。
Condition:
没有数据时,会阻塞所有的消费者线程,并释放独占锁,等待生产者生产数据,然后唤醒某个阻塞的消费者线程。
数据已满时,会阻塞所有的生产者线程,并释放独占锁,等待消费者消费数据,然后唤醒某个阻塞的生产者线程。
从上面的代码中我们可以看到Condition中两个重要的方法:await()和signal(),现在我们就来AQS是如何实现的。
await()
添加节点到condition的条件队列中
释放当前节点持有的锁
判断当前节点是否在阻塞队列中,可能节点已经被转移到阻塞队列中了
/**
* 判断当前节点是否在阻塞队列中
*/
final boolean isOnSyncQueue(Node node) {
//节点的waitStatus为CONDITION或者节点的前驱为null 说明不在阻塞队列中
//条件队列中的节点转移到阻塞队列中的时候waitStatus是为0的
//waitStatus为CONDITION 肯定表示节点还在条件队列中
//节点的前驱为null 条件队列中的节点前驱肯定为null 因为根本没有使用到
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
//如果节点的后继不为空说明在阻塞队列中 因为条件队列中的节点后继肯定是null
if (node.next != null)
return true;
return findNodeFromTail(node);
}
/**
* 直接从阻塞队列的尾部开始查找 找到就直接返回true
* @return true if present
*/
private boolean findNodeFromTail(Node node) {
Node t = tail;
for (;;) {
if (t == node)
return true;
if (t == null)
return false;
t = t.prev;
}
}
如果节点在被唤醒,且发现自己已经在阻塞队列中,会调用acquireQueued方法,这部分是AQS源码解析一中的内容,直接去尝试获取独占锁,代码我也贴一下。
/**
* 返回线程被中断的状态
* node的前驱是头节点 就再尝试获取一下锁
* 否则的话就挂起当前线程
* @return true 需要将当前线程标记为中断的 false 不用标记为中断的
*/
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {//死循环
//获取节点的前驱
final Node p = node.predecessor();
//节点前驱是头节点 就再尝试获取一下锁
if (p == head && tryAcquire(arg)) {
//设置当前节点为头节点
setHead(node);
p.next = null; //利于gc
failed = false;
return interrupted;
}
//判断node是不是需要被挂起且挂起当前线程并返回线程的中断状态
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)//如果是被中断的
cancelAcquire(node);
}
}
signal()
看看doSignal
重点来了,transferForSignal,将节点从条件队列转移到阻塞队列中与我们在**await()**中说到的这断代码有关联
//node还没有转移到阻塞队列中或者线程被中断都会终止循环
while (!isOnSyncQueue(node)) {
//不在阻塞队列中 挂起当前线程 当线程被唤醒或者是park的时候被中断了
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
再来看看transferForSignal
enq
/**
* 死循保证节点能插入到阻塞队列的队
* @return 返回node的前驱节点
*/
private Node enq(final Node node) {
for (;;) {//死循环
Node t = tail;
//尾节点为空 阻塞队列中还没节点
if (t == null) { // 必须初始化
//cas设置头节点 注意头节点是一个空节点
if (compareAndSetHead(new Node()))
//头尾指针都指向新new空的节点
tail = head;
} else {//一定会执行这步
//尾部插入node
node.prev = t;
//cas设置尾节点
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
看到这里可以知道,当转移到阻塞队列中了,节点的前驱被取消或者设置前驱的状态为SIGNAL的失败都可以直接唤醒当前线程。唤醒之后我们就会走**await()**中的,这段代码。
//在阻塞队列中了 尝试获取独占锁并设置成之前自己的重入次数
//node.nextWaiter != null 说明是在signal之前中断的
//node.nextWaiter == null 说明是在signal之后中断的
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
//节点的nextWaiter不为null 需要清空取消状态的节点
if (node.nextWaiter != null)
//从条件队列的头节点遍历 清除状态为取消状态的节点
unlinkCancelledWaiters();
//等于0说明没有发生中断 不等于0则说明发现了中断
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
Condition整体的流程到这里我们就分析完了,关于AbstractQueuedSynchronizer完整的源码解析点击这里。