Java多线程之生产者/消费者模式
1. 描述
生产者/消费者问题是研究多线程程序时绕不开的经典问题之一,它主要描述的是用一个缓冲区作为仓库,生产者可以将生产的产品放入仓库,消费者则可以从仓库中取走产品。
再详细一点的描述就是:在生产者和消费者之间共用一个容器,生产者生产的商品放到容器中(容器有一定的容量),消费者从容器中消费商品,当容器满了后,生产者等待,当容器为空时,消费者等待。当生产者将商品放入容器后,通知消费者消费;当消费者拿走商品后,通知生产者生产。
要解决生产者/消费者问题的一般方法是采用某种机制保护生产者和消费者之间的同步。
作为仓库或容器来说,它是被生产者和消费者所共享的资源,那如何保证同一个资源被多个线程并发访问时的完整性,这是同步问题的核心。
本文将介绍最常用到的方法。
2. wait()/notify()方法
wait()和notify()方法都是基类Object的方法,这就意味着所有的类都会有这两个方法,也就是说,我们可以为所有的Java对象实现同步机制。
我们先来看一下wait()方法与notify()方法的作用。
wait():在其他线程调用此对象的 notify() 方法或 notifyAll() 方法之前,导致当前线程等待。
notify():唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。
下面以一个实例来说明它们的使用。
假如我们开了一个馅饼店,馅饼按需出售。面点师将生产出来的馅饼放到一个篮子里,顾客来消费时,从篮子里取走馅饼。设定篮子的容量为1,即篮子里最多只允许放一个馅饼,那么,当面点师发现篮子里还有一个馅饼时,他就等待篮子变空后再生产一个馅饼放入篮子,并通知顾客可以消费了;当顾客发现篮子里没有馅饼时,他就等待篮子里有一个馅饼后再消费,消费结束后又通知面点师可以继续生产了。
这时,我们就需要保证以下几点:
- 同一时间内只能有一个生产者生产
- 同一时间内只能有一个消费者消费
- 生产者生产的同时消费者不能消费
- 消费者消费的同时生产者不能生产
- 容器空时消费者不能继续消费
- 容器满时生产者不能继续生产
示例:
馅饼类:
package com.demo;
/**
* 馅饼类
*
* @author 小明
*
*/
public class Pie {
private int id; // 编号
private int weight; // 重量
public Pie() {
super();
}
public Pie(int id, int weight) {
super();
this.id = id;
this.weight = weight;
}
// 省略getter/setter
// ……
@Override
public String toString() {
return "Pie [id=" + id + ", weight=" + weight + "]";
}
}
仓库类,我们实际共享的是放置馅饼的篮子,所以实现篮子对象的互斥:
package com.demo;
/**
* 仓库类,实现缓冲区
*
* @author 小明
*
*/
public class Storage {
private final int MAX_SIZE = 1; // 最大长度
private Pie[] basket; // 篮子,缓冲区
private int index; // 当前篮子中馅饼数量
public Storage() {
basket = new Pie[MAX_SIZE];
}
/**
* 生产馅饼然后将其放入篮子
*/
public void push() {
synchronized (basket) {
// 篮子容量不足,则等待
while (index >= MAX_SIZE) {
System.out.println("篮子已满,暂停生产");
try {
basket.wait(); // 阻塞
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 生产条件满足,则生产馅饼
basket[index++] = new Pie(Utils.generatePieId(),
295 + (int) (Math.random() * 5));
System.out.println("已经生产1个馅饼:" + basket[index - 1] + "\n当前库存量为:"
+ index);
// 通知消费者消费
basket.notify();
}
}
/**
* 消费馅饼从篮子中移除
*/
public void pop() {
synchronized (basket) {
// 库存量不足
while (index <= 0) {
System.out.println("消费库存量不足");
try {
basket.wait(); // 阻塞
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 消费条件满足,则消费馅饼
Pie pie = basket[--index];
basket[index] = null;
System.out.println("消费1个馅饼:" + pie + "\n当前库存量为:" + index);
// 通知生产者生产
basket.notify();
}
}
}
生成馅饼编号的工具类:
package com.demo;
/**
* 工具类
*
* @author 小明
*
*/
public class Utils {
private static int currentPieId = 1; // 当前馅饼编号字段
/**
* 生成馅饼编号
*
* @return 编号
*/
public static int generatePieId() {
return currentPieId++;
}
}
生产者:
package com.demo;
/**
* 生产者
*
* @author 小明
*
*/
public class Producer implements Runnable {
private Storage storage; // 生产时向仓库中放入资源
public Producer(Storage storage) {
this.storage = storage;
new Thread(this).start();
}
@Override
public void run() {
while (true) {
storage.push(); // 生产馅饼放入篮子
try {
Thread.sleep((int) (Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
消费者:
package com.demo;
/**
* 消费者
*
* @author 小明
*
*/
public class Consumer implements Runnable {
private Storage storage; // 消费时从仓库中获取资源
public Consumer(Storage storage) {
this.storage = storage;
new Thread(this).start();
}
@Override
public void run() {
while (true) {
storage.pop(); // 从篮子中消费馅饼
try {
Thread.sleep((int) (Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
测试:
package com.demo;
/**
* 测试类
*
* @author 小明
*
*/
public class Test {
public static void main(String[] args) {
Storage storage = new Storage();
Producer producer1 = new Producer(storage);
Consumer consumer1 = new Consumer(storage);
}
}
结果:
已经生产1个馅饼:Pie [id=1, weight=296]
当前库存量为:1
消费1个馅饼:Pie [id=1, weight=296]
当前库存量为:0
已经生产1个馅饼:Pie [id=2, weight=298]
当前库存量为:1
消费1个馅饼:Pie [id=2, weight=298]
当前库存量为:0
已经生产1个馅饼:Pie [id=3, weight=296]
当前库存量为:1
消费1个馅饼:Pie [id=3, weight=296]
当前库存量为:0
已经生产1个馅饼:Pie [id=4, weight=295]
当前库存量为:1
消费1个馅饼:Pie [id=4, weight=295]
当前库存量为:0
消费库存量不足
已经生产1个馅饼:Pie [id=5, weight=298]
当前库存量为:1
消费1个馅饼:Pie [id=5, weight=298]
当前库存量为:0
已经生产1个馅饼:Pie [id=6, weight=299]
当前库存量为:1
篮子已满,暂停生产
消费1个馅饼:Pie [id=6, weight=299]
当前库存量为:0