例如,我们有一个有界队列,有生产者向队列里插入元素,有消费者从队列中获取并删除元素。
如果队列满了,则要阻塞生产者,等队列不满了再添加元素;
如果队列里没有元素,则要阻塞消费者,等到有元素了再进行消费。
因此,当生产者向队列里放进一个元素后,或者当消费者消费了队列里的一个元素后,就可以notify被阻塞的线程。
我们通过一个简单的例子来看wait()方法的判断逻辑要用while判断,而不是用if判断
import java.util.ArrayList;
import java.util.List;
public class EarlyNotify {
// 有界队列
private List<String> list = new ArrayList<String>();
public String removeItem() throws InterruptedException {
print("进入方法 - removeItem()");
synchronized (list) {
if (list.isEmpty()) { // 这里用if语句会发生危险
print("in removeItem() - list为空 - wait()");
list.wait();
print("in removeItem() - 被唤醒- done with wait()");
}
// 删除元素
String item = (String) list.remove(0);
print("in removeItem() - 删除元素: '" + item + "'");
print("离开方法 - removeItem()");
return item;
}
}
public void addItem(String item) {
print("进入方法 - addItem()");
synchronized (list) {
// 添加元素
list.add(item);
print("in addItem() - 添加元素: '" + item + "'");
// 添加后,通知所有线程
list.notifyAll();
print("in addItem() - notifyAll线程");
}
print("离开方法 - addItem()");
}
private static void print(String msg) {
String name = Thread.currentThread().getName();
System.out.println(name + ": " + msg);
}
public static void main(String[] args) {
final EarlyNotify en = new EarlyNotify();
Runnable consumer = new Runnable() {
public void run() {
try {
String item = en.removeItem();
} catch (InterruptedException ix) {
print("interrupted!");
} catch (Exception x) {
print("threw an Exception!!!\n" + x);
}
}
};
Runnable producer = new Runnable() {
public void run() {
en.addItem("Hello!");
}
};
try {
// 启动第一个删除元素的线程
Thread threadConsumer1 = new Thread(consumer, "threadConsumer1");
threadConsumer1.start();
Thread.sleep(500);
// 启动第二个删除元素的线程
Thread threadConsumer2 = new Thread(consumer, "threadConsumer2");
threadConsumer2.start();
Thread.sleep(500);
// 启动增加元素的线程
Thread threadProducer = new Thread(producer, "threadProducer");
threadProducer.start();
// Thread.sleep(10000); // wait 10 seconds
// threadA1.interrupt();
// threadA2.interrupt();
} catch (InterruptedException x) {
}
}
}
打印结果:
threadConsumer1: 进入方法 - removeItem()
threadConsumer1: in removeItem() - list为空 - wait()
threadConsumer2: 进入方法 - removeItem()
threadConsumer2: in removeItem() - list为空 - wait()
threadProducer: 进入方法 - addItem()
threadProducer: in addItem() - 添加元素: 'Hello!'
threadProducer: in addItem() - notifyAll线程
threadConsumer2: in removeItem() - 被唤醒- done with wait()
threadProducer: 离开方法 - addItem()
threadConsumer2: in removeItem() - 删除元素: 'Hello!'
threadConsumer2: 离开方法 - removeItem()
threadConsumer1: in removeItem() - 被唤醒- done with wait()
threadConsumer1: threw an Exception!!!
java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
我们也知道,notify并不能定向的唤醒某一个或某一类被阻塞的线程,它只能随机的唤醒一个线程或唤醒所有的线程,这样的弊端就造成,队列还可以再放一个元素,一个生产线程被唤醒了,放进元素后,在notify或notifyAll其他的线程,有可能唤醒的还是生产者线程,那么该生产者线程判断条件不满足后再次wait(),这样就造成了低效,在ArrayBlockingQueue中,是使用的ReentrantLock,和notFull,notEmpty两个Condition来做的,notFull用来控制生产者的put,notEmpty来控制消费者的take