Condition
是一个配合独占锁ReentrantLock
实现类似Object
的wait
和notify
功能的等待队列。Object
的wait、notify
方法需要在synchronized
代码块中执行,同样Condition
的await、signal
需在lock
获取独占锁成功之后调用。与notify
只能唤醒锁对象上所有的阻塞线程相比,Condition
可以唤醒指定的阻塞线程,更加灵活。
不管是notify
还是signal
,它们都是唤醒线程进行锁的竞争,所以被唤醒的线程不一定会获取锁成功,很有可能会竞争锁失败而重新进入阻塞状态。
案例
public class ConditionDemo {
int queueSize = 5;
PriorityQueue queue = new PriorityQueue(queueSize);
private ReentrantLock lock = new ReentrantLock();
Condition notFullCondition = lock.newCondition();
Condition notEmptyCondition = lock.newCondition();
//消费者
class Producer implements Runnable{
@Override
public void run() {
while (true){
try {
lock.lock();
//消费者已经满了
if(queue.size() == queueSize){
System.out.println("消息队列已经满了");
//生产者进入阻塞,线程添加到notFullCondition等待队列
notFullCondition.await();
}
double val = Math.random();
queue.offer(val);
System.out.println("生产一条消息"+val+"队列剩余大小"+(queueSize - queue.size()));
Thread.sleep(1000);
notEmptyCondition.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
}
class Consumer implements Runnable{
@Override
public void run() {
while (true){
try{
lock.lock();
if(queue.isEmpty()){
//队列已经空了
System.out.println("消息队列内的消息已经全部被消费完");
//消费者进入阻塞,线程被添加到notEmptyCondition队列
notEmptyCondition.await();
}
Object val = queue.poll();
System.out.println("消费消息:"+val+",剩余"+queue.size()+"没有消费");
Thread.sleep(1000);
notFullCondition.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
}
public static void main(String[] args) {
ConditionDemo conditionDemo = new ConditionDemo();
Thread producerThread = new Thread(conditionDemo.new Producer());
Thread consumerThread = new Thread(conditionDemo.new Consumer());
producerThread.start();
consumerThread.start();
}
}
执行结果:
ReentrantLock
在非公平锁的情况下,生产者一直竞争到了锁,直到消息队列满了进入阻塞状态,消费者才竞争到了锁。如果将锁设置成公平锁,可以看到生产者和消费者轮流获取到锁。
生产一条消息0.5278959497722281队列剩余大小4
生产一条消息0.4028658058829394队列剩余大小3
生产一条消息0.630037443523446队列剩余大小2
生产一条消息0.1644722338258895队列剩余大小1
生产一条消息0.14783073976448924队列剩余大小0
消息队列已经满了
消费消息:0.14783073976448924,剩余4没有消费
消费消息:0.1644722338258895,剩余3没有消费
消费消息:0.4028658058829394,剩余2没有消费
消费消息:0.5278959497722281,剩余1没有消费
消费消息:0.630037443523446,剩余0没有消费
消息队列内的消息已经全部被消费完
源码分析
await()线程进入等待
如上图所示:线程调用await
方法后,ReentrantLock
阻塞队列中的线程节点将追加到Condition
的阻塞队列的末尾。
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//1.将当前线程保装成一个node对象,并加入到Condition阻塞队列
Node node = addConditionWaiter();
//2.释放线程持有的所有的锁
int savedState = fullyRelease(node);
int interruptMode = 0;
//3.判断线程是否在aqs阻塞队列中
while (!isOnSyncQueue(node)) {
//如果没有就阻塞线程
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
//4.线程被唤醒后要重新添加入AQS等待队列
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
//过滤掉被取消的线程
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
-
将当前线程封装成一个
Node
对象,并加入到Condition
阻塞队列的末尾。Condition
调用await
方法是在线程已经获取到锁的前提下,所以不存在并发问题。private Node addConditionWaiter() { //Condition等待队列的尾节点 Node t = lastWaiter; // 过滤掉被取消的线程 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; }
- 释放线程持有的所有的锁。
一个线程进入
Condition
阻塞队列,就必须将它获取的所有锁都释放掉。ReentrantLock
是一个可重入锁,getState()
可能是一个大于1
的数字,所以调用release
方法时,不能使用release(1)
,而是release(getState())
。在
ReentrantLock
独占锁中,线程执行释放锁的操作就是将自己的节点的状态设置成0
,然后唤醒后继的阻塞线程,后继的线程成功获取锁后,会将自己设置成aqs
的头节点,最终达到了节点移除的操作。final int fullyRelease(Node node) { boolean failed = true; try { //获取锁的状态,savedState表示一个线程获取锁的全部次数 int savedState = getState(); //释放锁,release(savedState)将所有的锁都释放掉 if (release(savedState)) { failed = false; return savedState; } else { throw new IllegalMonitorStateException(); } } finally { if (failed) node.waitStatus = Node.CANCELLED; } }
-
判断线程是否在
AQS
阻塞队列中。当
Condition
调用await
方法后,线程会释放锁从AQS
阻塞队列中移除并加入到Condition
阻塞队列中,所以遍历AQS
队列来判断是否要调用LockSupport.park(this);
方法。
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; //从尾部遍历aqs队列,新增的节点都是追加在队列的末尾,所以从后遍历效率更高。 return findNodeFromTail(node); }
-
线程被唤醒后要重新添加入AQS等待队列,这个逻辑与
ReentrantLock
的加锁逻辑是一样的。参考(https://blog.csdn.net/YANYONG_/article/details/130630856)。
signal()唤醒操作
唤醒Condition
阻塞队列中的阻塞线程时,线程执行signal
方法时必须持有锁。
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
// Condition等待队列的第一个线程节点
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
//1.将Condition等待队列的第一个线程投递到AQS等待队列
} while (!transferForSignal(first) &&(first = firstWaiter) != null);
}
-
将Condition等待队列的第一个线程投递到
AQS
等待队列。final boolean transferForSignal(Node node) { //重试设置 node 节点的状态 if (!compareAndSetWaitStatus(node, Node.CONDITION, 0)) return false; //追加到AQS等待队列的末尾,并返回它的前置节点 Node p = enq(node); int ws = p.waitStatus; //如果前置节点的状态为取消或它的节点的状态设置为SIGNAL失败,直接唤醒待唤醒的节点; //如果前置节点状态设置成功,则当前节点等待它的前置节点来唤醒它。 if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL)) LockSupport.unpark(node.thread); return true; }