Condition是一个多线程协调通信的工具类,
Condition是对线程进行控制管理的接口,具体实现是AQS的一个内部类ConditionObject,可以让某些线程一起等待某个条件(condition),只有满足条件时,线程才会被唤醒
Condition是一个接口,这些是抽象方法
我们本篇文章主要看的就是await()和signal()
首先了解一下AQS同步队列与等待队列数据结构
等待队列是一个FIFO单向链表
同步队列是一个FIFO双向链表
下面来看一个生产者消费者对Condition的应用场景
生产者
public class Producer implements Runnable{
private Queue<String> msg;
private int maxSize;
Lock lock;
Condition condition;
public Producer(Queue<String> msg, int maxSize, Lock lock, Condition condition) {
this.msg = msg;
this.maxSize = maxSize;
this.lock = lock;
this.condition = condition;
}
@Override
public void run() {
int i=0;
while(true){
i++;
lock.lock();
while(msg.size()==maxSize){
System.out.println("生产者队列满了,先等待");
try {
condition.await(); //阻塞线程并释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("生产消息:"+i);
msg.add("生产者的消息内容"+i);
condition.signal(); //唤醒阻塞状态下的线程
lock.unlock();
}
}
}
消费者
public class Consumer implements Runnable{
private Queue<String> msg;
private int maxSize;
Lock lock;
Condition condition;
public Consumer(Queue<String> msg, int maxSize, Lock lock, Condition condition) {
this.msg = msg;
this.maxSize = maxSize;
this.lock = lock;
this.condition = condition;
}
@Override
public void run() {
int i=0;
while(true){
i++;
lock.lock(); //synchronized
while(msg.isEmpty()){
System.out.println("消费者队列空了,先等待");
try {
condition.await(); //阻塞线程并释放锁 wait
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("消费消息:"+msg.remove());
condition.signal(); //唤醒阻塞状态下的线程
lock.unlock();
}
}
}
测试类,需要注意,生产者消费者需要互斥,共同竞争同一把锁,且Condition也要是同一个对象
public class App
{
public static void main( String[] args )
{
Queue<String> queue=new LinkedList<>();
Lock lock=new ReentrantLock(); //重入锁
Condition condition=lock.newCondition();
int maxSize=5;
Producer producer=new Producer(queue,maxSize,lock,condition);
Consumer consumer=new Consumer(queue,maxSize,lock,condition);
Thread t1=new Thread(producer);
Thread t2=new Thread(consumer);
t1.start();
t2.start();
}
}
就当消费者先拿到锁把。这样可以少循环几次生产者。当消费者拿到锁之后,发现队列是空的,就执行wait方法并阻塞,然后执行生产者,当生产者生产满了之后,进入wait方法,唤醒消费者。依次往复,下面跟踪源码看逻辑
public final void await() throws InterruptedException {
if (Thread.interrupted())//1. 如果中断线程直接抛异常
throw new InterruptedException();
//2. 将当前线程添加到JUC等待队列,如果队列不存在,将当前线程封装成Node节点 firstWaiter、lastWaiter均指向这个节点
Node node = addConditionWaiter();
//3. 释放锁,返回当前锁持有JUC state次数
int savedState = fullyRelease(node);
int interruptMode = 0;
//4.如果一个节点(始终是最初放置在条件队列上的节点)现在正等待在同步队列上重新获取,则返回true。
while (!isOnSyncQueue(node)) {
//5. 阻塞线程
LockSupport.park(this);
//6. 等待时检查中断。
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
//被唤醒后,尝试拿到锁,并增加线程中断判断,如果线程被中断,也会走到这里,并进入AQS阻塞方法,判断是否被中断过,如果如果中断过直接中断线程
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // 清理掉不为-2的线程
unlinkCancelledWaiters();
if (interruptMode != 0)//如果中断标识不为空,进行中断处理
reportInterruptAfterWait(interruptMode);
}
第一步不需要看了,直接看第二步吧
private Node addConditionWaiter() {
//拿到最后一个节点
Node t = lastWaiter;
// 如果最后一个节点被取消了,就清理。
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
//创建一个新的Node节点
Node node = new Node(Thread.currentThread(), Node.CONDITION);
//如果最后一个节点是空的,说明整个链表都是空的,就将首尾都指向新建的Node
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
//返回新建的Node节点
return node;
}
- 释放锁,返回释放次数
final int fullyRelease(Node node) {
boolean failed = true;
try {
//获取当前线程的持有锁的state次数
int savedState = getState();
//有几次都直接释放掉,并返回释放次数
if (release(savedState)) {
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
//释放失败的话,说明线程有问题,直接标记cancel
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
public final boolean release(int arg) {
//尝试释放锁,释放成功的话返回true,执行锁唤醒逻辑,
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
private void unparkSuccessor(Node node) {
//清除等待状态,将同步队列head节点的状态修改为0
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
//获取线程阻塞,说明当前线程就是同步队列的head,先获取下一个节点,如果下一个节点不为空且为正常状态,就唤醒第二个节点
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
final boolean isOnSyncQueue(Node node) {
//判断当前状态是新建Node的状态,或者当前Node在AQS同步队列里,判断是否在同步队列里,就看这个节点是否有前一个节点,如果有,说明在队列中。
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);
}
- 将线程挂起,现在现在就在等待队列中阻塞了
现在第一个线程阻塞了。那么现在看第二个线程。第二个线程需要调用signal()方法来进行一些操作
public final void signal() {
//如果操作的不是当前拿到锁的线程,抛出异常
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
//头节点不为空,就执行
if (first != null)
doSignal(first);
}
//这里主要的逻辑是将等待队列的firstWaiter移动到同步队列的尾部,然后将firstWaiter修改为下一个等待队列的节点,如果没有了就变成null。判断移动到同步队列的节点状态,如果为cancel或修改状态为SIGNAL失败,唤醒以重新同步
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
然后ThreadB调用awit()方法,唤醒同步队列的ThreadA,消费者需要从阻塞的位置退出继续执行
ThreadB进入等待队列,一直重复
而对于Condition的signalAll()方法,相当于对等待队列中的每个节点均执行一次signal()方法,效果就是将等待队列中所有节点全部移动到同步队列中,并唤醒每个节点的线程。
等待队列不像同步队列的中断一样,中断后标记中断标识,等Node拿到锁后才响应中断。等待队列会立即响应,将需要中断的Node放入同步队列,并将状态修改为0
Condition的使用场景:JUC包中提供了很多BlockQueue,可以直接使用