什么是生产者/消费者模型
生产者和消费者在同一时间段内共用同一段存储空间,生产者向空间里生产数据,而消费者取走数据。
场景模拟
工厂里面汽车的零件生产与汽车组装是同时进行的,其中我们定义以下规则
- 汽车组装必须有足够的零件支持
- 零件生产与汽车组装(有足够的零件)互不影响
- 为避免浪费,零件的储存上限为5(我们抽象为1个零件可以生产一辆汽车)
下面我们使用wait() / notify()方法与await() / signal()方法对我们的场景进行模拟
wait() / notify()方法
在这里我们对wait() / notify()进行一个简单的介绍
- wait():当缓冲区已满/空时,生产者/消费者线程停止自己的执行,放弃锁,使自己处于等待状态,让其他线程执行。
- notify()方法:当生产者/消费者向缓冲区放入/取出一个产品时,向其他等待的线程发出可执行的通知,同时放弃锁,使自己处于等待状态
//缓冲区
class Storage{
//存储零件
private LinkedList<Object> list = new LinkedList<Object>();
//最大存储数量
private int MAX_NUMBER = 5;
//生产零件计数器
private int produceCount = 0;
//组装零件计数器
private int consumeCount = 0;
//生产零件
public void produce() {
synchronized (list) {
//零件太多,停止生产
while(list.size() >= MAX_NUMBER) {
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//恢复生产
Object object = new Object();
list.add(object);
System.out.println("生产第" + (++produceCount) + "个零件,仓库还有" + list.size() + "个零件");
list.notify();
}
}
//组装零件
public void consume() {
synchronized (list) {
//零件太少,无法组装
while(list.size() <= 0) {
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//恢复组装
list.removeFirst();
System.out.println("组装第" + (++consumeCount) + "个零件,仓库还有" + list.size() + "个零件");
list.notify();
}
}
}
//生产者,即生产零件的工作小组
class Producer implements Runnable{
private Storage storage;
public Producer(Storage storage) {
this.storage = storage;
}
@Override
public void run() {
//生产50个零件
for(int i=0;i<50;i++) {
storage.produce();
}
}
}
//消费者,即组装零件的工作小组
class Consumer implements Runnable{
private Storage storage;
public Consumer(Storage storage) {
this.storage = storage;
}
@Override
public void run() {
//组装50个零件
for(int i=0;i<50;i++) {
storage.consume();
}
}
}
public class Demo{
public static void main(String[] args) {
Storage storage = new Storage();
new Thread(new Consumer(storage)).start();
new Thread(new Producer(storage)).start();
}
}
部分运行结果,读者可以自行运行上面那段代码,在自己电脑查看完整结果
生产第1个零件,仓库还有1个零件
组装第1个零件,仓库还有0个零件
生产第2个零件,仓库还有1个零件
生产第3个零件,仓库还有2个零件
组装第2个零件,仓库还有1个零件
组装第3个零件,仓库还有0个零件
生产第4个零件,仓库还有1个零件
组装第4个零件,仓库还有0个零件
生产第5个零件,仓库还有1个零件
生产第6个零件,仓库还有2个零件
生产第7个零件,仓库还有3个零件
生产第8个零件,仓库还有4个零件
生产第9个零件,仓库还有5个零件
组装第5个零件,仓库还有4个零件
组装第6个零件,仓库还有3个零件
组装第7个零件,仓库还有2个零件
组装第8个零件,仓库还有1个零件
组装第9个零件,仓库还有0个零件
生产第10个零件,仓库还有1个零件
生产第11个零件,仓库还有2个零件
生产第12个零件,仓库还有3个零件
生产第13个零件,仓库还有4个零件
生产第14个零件,仓库还有5个零件
组装第10个零件,仓库还有4个零件
组装第11个零件,仓库还有3个零件
(略)
await() / signal()方法
await()和signal()的功能基本上和wait() / nofity()相同,完全可以取代它们,但是它们和Lock直接挂钩,具有更大的灵活性。通过在Lock对象上调用newCondition()方法,将条件变量和一个锁对象进行绑定,进而控制并发程序访问竞争资源的安全。
下面我们进行代码模拟
//缓冲区
class Storage{
//锁
private final Lock lock = new ReentrantLock();
//仓库满的条件变量
private final Condition full = lock.newCondition();
//仓库空的条件变量
private final Condition empty = lock.newCondition();
//存储零件
private LinkedList<Object> list = new LinkedList<Object>();
//最大存储数量
private int MAX_NUMBER = 5;
//生产零件计数器
private int produceCount = 0;
//组装零件计数器
private int consumeCount = 0;
//生产零件
public void produce() {
lock.lock();
//零件太多,停止生产
while(list.size() >= MAX_NUMBER) {
try {
full.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//恢复生产
Object object = new Object();
list.add(object);
System.out.println("生产第" + (++produceCount) + "个零件,仓库还有" + list.size() + "个零件");
empty.signal();
lock.unlock();
}
//组装零件
public void consume() {
lock.lock();
//零件太少,无法组装
while(list.size() <= 0) {
try {
empty.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//恢复组装
list.removeFirst();
System.out.println("组装第" + (++consumeCount) + "个零件,仓库还有" + list.size() + "个零件");
full.signal();
lock.unlock();
}
}
//生产者,即生产零件的工作小组
class Producer implements Runnable{
private Storage storage;
public Producer(Storage storage) {
this.storage = storage;
}
@Override
public void run() {
//生产50个零件
for(int i=0;i<50;i++) {
storage.produce();
}
}
}
//消费者,即组装零件的工作小组
class Consumer implements Runnable{
private Storage storage;
public Consumer(Storage storage) {
this.storage = storage;
}
@Override
public void run() {
//组装50个零件
for(int i=0;i<50;i++) {
storage.consume();
}
}
}
public class Demo{
public static void main(String[] args) {
Storage storage = new Storage();
new Thread(new Consumer(storage)).start();
new Thread(new Producer(storage)).start();
}
}
结果一切正常,证明代码无误
生产者/消费者模型的优势
- 简化开发,生产者与消费者只需要知道共享对象即可,而不需要知道彼此的存在
- 将生产者与消费者解耦,使代码简洁、可读、易维护