Java并发编程之生产者/消费者模型

什么是生产者/消费者模型

生产者和消费者在同一时间段内共用同一段存储空间,生产者向空间里生产数据,而消费者取走数据。

场景模拟

工厂里面汽车的零件生产与汽车组装是同时进行的,其中我们定义以下规则

  1. 汽车组装必须有足够的零件支持
  2. 零件生产与汽车组装(有足够的零件)互不影响
  3. 为避免浪费,零件的储存上限为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();
	}
}

结果一切正常,证明代码无误

生产者/消费者模型的优势
  • 简化开发,生产者与消费者只需要知道共享对象即可,而不需要知道彼此的存在
  • 将生产者与消费者解耦,使代码简洁、可读、易维护

参考:生产者/消费者问题的多种Java实现方式

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值