概述
生产者与消费者共用一个队列。
- 当队列满时,生产者无法再继续生产,所以生产者阻塞;
- 当队列空时,消费者无法再继续消费,所以消费者阻塞。
关键操作模拟
- 队列:本文以LinkedList作为队列,有兴趣的朋友也可以使用其他队列(比如循环队列,阻塞队列等)。
- 阻塞:我们以Object类的wait()方法使线程阻塞(此处蕴含细节:为什么wait()和notifyAll()方法属于Object类,而不属于线程?参考wait()、notify()和notifyAll()方法为什么属于Object)。
- 唤醒:我们以Object类的notifyAll()方法唤醒被阻塞的线程(此处蕴含细节,notify()与notifyAll()方法有什么不同?参考Object的notify和notifyAll方法的区别)。
代码实现
队列实现
public class Storage {
private static final int MAX_SIZE = 10;
private static LinkedList<String> queue = new LinkedList<String>();
public void produce(String s) {
synchronized (queue) {
while (queue.size() == MAX_SIZE) {
System.out.println("仓库已满");
try {
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("生产了 " + s);
queue.add(s);
queue.notifyAll();
}
}
public String consumer() {
synchronized (queue) {
while (queue.size() == 0) {
System.out.println("仓库已空");
try {
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
String s = queue.pollFirst();
queue.notifyAll();
System.out.println("消费了 " + s);
return s;
}
}
}
生产者实现
public class Producer extends Thread {
private Storage storage;
public Producer(Storage storage) {
this.storage = storage;
}
public void run() {
for (int i = 0; i < 100; i++) {
storage.produce(String.valueOf(i));
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
消费者实现
class Consumer extends Thread {
private Storage storage;
public Consumer(Storage storage) {
this.storage = storage;
}
public void run() {
while(true) {
storage.consumer();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
测试实现
public class Test {
public static void main(String[] args) {
Storage storage = new Storage();
Producer producer = new Producer(storage);
Consumer consumer = new Consumer(storage);
producer.start();
consumer.start();
}
}
打印结果
生产了 0
消费了 0
生产了 1
生产了 2
生产了 3
生产了 4
生产了 5
生产了 6
生产了 7
生产了 8
生产了 9
生产了 10
仓库已满
消费了 1
生产了 11
仓库已满
消费了 2
生产了 12
仓库已满
消费了 3
生产了 13
仓库已满
消费了 4
生产了 14
仓库已满
消费了 5
生产了 15
仓库已满
消费了 6
.
.
.
代码解惑
解惑
Storage的produce和consume方法中,为什么判断队列满和判断队列空时使用while,而不是使用if呢?以producer方法为例,代码为
synchronized (queue) {
while (queue.size() == MAX_SIZE) {
System.out.println("仓库已满");
try {
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("生产了 " + s);
queue.add(s);
queue.notifyAll();
}
简化为
synchronized (queue) {
while (queue.size() == MAX_SIZE) {
queue.wait();
}
System.out.println("生产了 " + s);
queue.add(s);
queue.notifyAll();
}
有的同学认为此处可以用if,但是经过仔细推敲,其实用if还是有问题的。此处我们模拟两个线程t1和t2,并认为此时队列已满,按照以下流程执行
- t1获取锁并进入同步代码,并执行while方法体,由于队列已满,则t1调用wait进入阻塞状态,并放弃锁。
- 此时t2恰好获取到了锁,也执行while方法体,由于此时队列已满,t2调用wait也进入阻塞状态,并放弃锁。
- 假设此时有一个消费者线程从队列消费了一个产品,并调用了notifyAll()方法,唤醒了t1和t2,假设t1又抢占到了锁,继续执行while,此时不满足
queue.size() == MAX_SIZE,则开始生产,最后执行notifyAll()方法,此过程并无问题。 - 接下来,假如t2恰好获取到了锁,并开始往下执行,因为此时队列已满,并不能继续生产,如果使用while语句,则会继续判断queue.size() == MAX_SIZE条件,使t2继续阻塞,但是如果使用if语句,则不会继续判断,而是直接去生产,而此时队列已满,肯定会出现异常。
总结
我们以生产的过程,解释了为什么此处必须使用while而不是使用if,消费的过程与生产过程类似,此处不再赘述。有兴趣的同学也可以参考一下EffectJava第三版,里面有关于while问题的详细叙述。
温馨提示
- 如果您对本文有疑问,请在评论部分留言,我会在最短时间回复。
- 如果本文帮助了您,也请评论,作为对我的一份鼓励。
- 如果您感觉我写的有问题,也请批评指正,我会尽量修改。