1、什么是Condition?
任意一个Java对象,都拥有一组监视器方法(定义在java.lang.Object上),主要包括wait()、 wait(long timeout)、notify()以及notifyAll()方法,这些方法与synchronized同步关键字配合,可以 实现等待/通知模式。Condition接口也提供了类似Object的监视器方法,与Lock配合可以实现等 待/通知模式,但是这两者在使用方式以及功能特性上还是有差别的。(并发编程艺术的概念)
2、Object的监视器方法Condition接口方法以及功能对比。
3、Condition接口的主要方法:
4、Condition接口的主要实现
主要是实现有:
1、AbstractQueuedLongSynchronizer.ConditionObject.class
2、AbstractQueuedSynchronizer.ConditionObject.class
5、Condition的使用方式
1、获取一个Condition实例,必须是使用Lock的newCondition()方法,同一个lock可以创建多个Condition实例。
2、使用案例-->实现自己的阻塞队列:
我们使用ReentrantLock + Condition 来实现一个阻塞队列,实现说明,我们实现的这个队列是有界队列,当队列满了以后,在往里面添加元素的话将会阻塞当前线程,同样的当队列空了,获取队列元素的线程也将阻塞,直到获取到队列的元素为止。
/**
* 使用ReentrantLock + Condition 来实现阻塞队列。
*
* @param <T>
*/
public class MyBlockingQueue<T> {
private Object[] elements;
private Lock lock = new ReentrantLock();
/*
添加元素的(生产者)Condition,主要做两件事情:
1、在队列满了的时候,将后续的生产者线程添加到其条件队列中并阻塞。
2、在消费者消费的时候(表示队列有空位了),唤醒阻塞的生产者线程继续生产。
*/
private Condition producerCondition = lock.newCondition();
/*
获取元素的(消费者)Condition,主要做两件事情:
1、在队列被消费空了的时候,将后续的消费者线程添加到其条件队列中并阻塞。
2、在消生产者生产的时候(表示队列里有新加的元素了,消费者可以消费了),唤醒阻塞的消费者线程继续消费。
*/
private Condition consumerCondition = lock.newCondition();
private int addIndex = 0;
private int removeIndex = 0;
private int count = 0;
public MyBlockingQueue(int queueSize) {
this.elements = new Object[queueSize];
}
public void add(T t) throws InterruptedException {
lock.lock();
try {
while (count == elements.length) {
/*
如果生产满了后,后续来添加元素的线程添加到producerCondition的条件队列中等待。
*/
producerCondition.await();
}
elements[addIndex] = t;
if (++addIndex == elements.length) {
addIndex = 0;
}
++count;
//每生产一个元素,就唤醒在等待的消费者去消费。
consumerCondition.signal();
} finally {
lock.unlock();
}
}
public T get() throws InterruptedException {
lock.lock();
try {
while (count == 0) {
/*
当消费完了后,还有消费者来消费的话,那就将其放在consumerCondition的条件队列中等待。
*/
consumerCondition.await();
}
Object x = elements[removeIndex];
if (++removeIndex == elements.length) {
removeIndex = 0;
}
--count;
//每当消费者消费一个,就通知在等待的生产者添加元素。
producerCondition.signal();
return (T) x;
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
MyBlockingQueue<String> myBlockingQueue = new MyBlockingQueue<>(5);
for (int i = 0; i <= 5; i++) {
int j = i;
new Thread(() -> {
try {
myBlockingQueue.add("message" + j);
System.out.println("add success message" + j);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "T1").start();
}
Thread.sleep(2000L);
new Thread(() -> {
String s = null;
try {
s = myBlockingQueue.get();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("获取message" + s);
}).start();
Thread.sleep(10000L);
}
}
6、Condition实现原理分析:
1、ConditionObject是同步器AbstractQueuedSynchronizer的内部类,因为Condition的操作需要 获取相关联的锁,所以作为同步器的内部类也较为合理。每个Condition对象都包含着一个队列(以下称为等待队列),我们的案例中创建了两个Condition实例,因此会有两个等待队列。
2、等待队列的基本结构:
3、我们使用4条线程来模拟一个过程来说明Condition的实现原理
threadA、threadB、threadC、threadD
步骤1:threadA获取到锁lock开始执行,threadB、threadC未获取到锁,进入AQS同步队列,那么lock的情况如下:
步骤2:threadA 调用condition.await()方法,因为condition是使用lock来创建的,因此在threadA调用await()方法时。
第一件事就是将threadA构建好节点,放入到condition实例的等待队列里面。
第二件事情就是释放做锁lock。
第三件事就是阻塞threadA线程。
我们假设在threadA释放锁后,threadD插队获取到了lock,然后threadD执行了condition.await()。我们假设threadD调用condition.await()后,threadB获取到了lock。 那么此时lock锁的情况就是如下:
步骤三:此时threadB时锁lock的持有者,吃喝threadB调用了condition.signal()方法,调用signal方法会condition队列中的firstWiter节点移动到AQS的sync(同步队列中),让其去竞争锁,那么就是如下情况:
以上就是condition的整体实现原理,其实实现很简单,condition.await()方法主要工作就是1、将调用condition.await()方法的线程构建节点添加到同步队列中。2、完完全全释放锁。3、阻塞调用condition.await()方法的线程。 而调用condition.await()方法的线程只有在其他线程调用condition.signal()方法才会被放入到AQS的同步队列中等待获取锁,一旦获取到锁,那就是被唤醒了。