生产者和消费者模式是面试时很容易被问到的一类题,在平常的开发中也经常碰到,比如在网游开发中:用一个线程把收到的字节数据封装起来写到一个队列中,然后用一个或多个线程从该队列中把数据读取出来再分发。本文来实现类似的情景:5个通宵加班并饿了一天的程序员去包子店吃小笼包,小笼包分为菜包和肉包,随机分配的,由于公司只给报销20个包子的钱,所以就只点了20个包子,包子店的老板娘一次只能蒸10个包子,而且是必须等他们吃完了蒸好的10个包子之后再继续做剩下的10个包子,由于这个世界本身是不公平的,所以抢的快的的就多吃点,但为了避免他们把包子抢烂,规定每次只能一个人去拿包子
这里生产者就是做包子的,消费者就是这5个饿晕了的coder,下面来看看怎么用代码实现它。
1. 创建包子店BreadShop.java,首先创建一些公用的数据

	// 装包子的盘子
	private ArrayList<Bread> breadList = new ArrayList<Bread>();
	// 包子的种类:肉包和菜包
	private BreadType breadTypes[] = { BreadType.MEAT, BreadType.VEGETABLES };
	// 已经出炉的包子总数
	private int totalCount = 0;
	// 点的包子数
	private final int MAX_COUNT = 20;

	enum BreadType {
		MEAT, VEGETABLES
	}

	// 包子
	class Bread {
		public BreadType type;

		public Bread(BreadType type) {
			this.type = type;
		}
	}

2. 首先来了解一下代码中用到的几个方法:wait()、sleep()、notify()、notifyAll()
wait()和notify()以及notifyAll()是Object的方法,这三个方法跟锁有密切关系,必须写在synchronized代码块中。obj.wait()表示线程会释放对象obj的锁,处于等待状态,直到有其他线程调用obj对象的notify()或者notifyAll()函数,才有机会重新去竞争obj对象的锁。notify()和notifyAll()的区别:前者是唤醒等待池中的一个线程,然后系统会让它获得obj对象的锁;后者是唤醒所有等待该对象的线程,然后让它们去竞争obj对象的锁。虽然使用notify()或者notifyAll()都可以,但大多数情况下还是notifyAll()安全些,notify()可能由于一些代码缺陷出现死锁,特别是在一个生产者和多个消费者的模式中。
sleep()是Thread的函数,它指定线程休眠一段时间,但是不会释放对象的锁。
下面来实现生产者

	class Product implements Runnable {
		private boolean isWork = false;

		public Product()
		{
			this.isWork = true;
		}

		// 把包子蒸熟后放到盘子里
		public void makeBread(Bread bread) {
			breadList.add(bread);
			switch(bread.type)
			{
			case MEAT:
				System.out.println("make a meat bread");
				break;

			case VEGETABLES:
				System.out.println("make a vegetables bread");
				break;
			}
		}

		@Override
		public void run() {
			while(isWork)
			{
				try {
					synchronized(breadList)
					{
						// 他们还没吃完,继续等待
						if(breadList.size() > 0)
							breadList.wait();
						// 一次蒸10个包子
						for(int i = 0; i < 10; ++ i)
						{
							int type = new Random().nextInt(2);
							Bread bread = new Bread(breadTypes[type]);
							this.makeBread(bread);
						}
						totalCount += 10;
						// 通知他们可以吃包子了
						breadList.notifyAll();
					}
					// 做完了20个包子
					if(totalCount >= MAX_COUNT)
					{
						isWork = false;
					}
				}catch(Exception e) {
					e.printStackTrace();
					isWork = false;
				}
			}

		}
	}

盘子里如果有包子,老板娘则等他们吃完;如果盘子里没有包子,则立即做10个包子,蒸熟后通知那5个coder来拿包子,直到做完20个包子。
3. 实现消费者

	class Consumer implements Runnable
	{

		private int id;
		public Consumer(int id)
		{
			this.id = id;
		}
		// 吃包子
		public void eat(Bread bread)
		{
			BreadType type = bread.type;
			switch(type)
			{
			case MEAT:
				System.out.println("AlexZhou " + id + " eat a meat bread");
				break;

			case VEGETABLES:
				System.out.println("AlexZhou " + id + " eat a vegetables bread");
				break;
			}
		}
		@Override
		public void run() {
			while(true)
			{
				try{
					synchronized(breadList)
					{
						// 包子还没做好
						if(breadList.size() == 0)
						{
							// 吃完了所有包子
							if(totalCount >= MAX_COUNT)
								break;
							// 通知老板娘赶快做包子
							breadList.notifyAll();
							// 等老板娘做包子
							breadList.wait();
						}
						else
						{
							// 从盘子里拿包子吃
							Bread bread = breadList.remove(0);
							this.eat(bread);

						}
					}
					// 这里模拟吃包子的时间,也可以增大其他线程获得锁的概率,提高公平性
					Thread.sleep(100);

				}catch(Exception e)
				{
					e.printStackTrace();
					break;
				}
			}

		}
	}

盘子里如果有包子就去拿包子,拿到后就跑到角落里去吃,吃完后继续去拿;盘子里如果没有包子就叫老板娘继续做包子,直到吃完20个。
4. 现在来模拟这个情景

	public static void main(String[] args) {
		BreadShop bs = new BreadShop();
		// 5个coder来到包子店点包子
		for(int i = 0; i < 5; ++ i)
		{
			Thread t = new Thread(bs.new Consumer(i));
			t.start();
		}
		// 老板娘开始做包子 
		Thread productThread = new Thread(bs.new Product());
		productThread.setPriority(Thread.MAX_PRIORITY);
		productThread.start();
	}

打印的结果如下:
make a meat bread
make a vegetables bread
make a vegetables bread
make a vegetables bread
make a meat bread
make a vegetables bread
make a meat bread
make a meat bread
make a meat bread
make a vegetables bread
AlexZhou 1 eat a meat bread
AlexZhou 3 eat a vegetables bread
AlexZhou 2 eat a vegetables bread
AlexZhou 0 eat a vegetables bread
AlexZhou 4 eat a meat bread
AlexZhou 3 eat a vegetables bread
AlexZhou 1 eat a meat bread
AlexZhou 2 eat a meat bread
AlexZhou 0 eat a meat bread
AlexZhou 4 eat a vegetables bread
make a meat bread
make a meat bread
make a vegetables bread
make a meat bread
make a meat bread
make a vegetables bread
make a meat bread
make a meat bread
make a meat bread
make a meat bread
AlexZhou 1 eat a meat bread
AlexZhou 2 eat a meat bread
AlexZhou 4 eat a vegetables bread
AlexZhou 1 eat a meat bread
AlexZhou 2 eat a meat bread
AlexZhou 3 eat a vegetables bread
AlexZhou 0 eat a meat bread
AlexZhou 4 eat a meat bread
AlexZhou 2 eat a meat bread
AlexZhou 1 eat a meat bread

lua面向对象模拟简介

lua不是面向对象语言,但可以通过表(table)和元表(metatable)来模拟。table 是 lua 中唯一的一种数据结构,它可以用来描述原始的数组、符号表、集合、 记录、...

阅读全文

在c/c++中调用lua函数

上篇文章完成了在lua中调用c/c++函数,现在来实现在c/c++中调用lua函数。 首先完成lua代码,创建sum.lua: function add(x, y) return x + y; end 为了...

阅读全文

在lua中调用c/c++函数

lua是一种轻量级的脚本语言,用来扩展c和c++非常好,在游戏开发中使用很普遍。 首先下载lua,因为我是在win7下,所以我这里下载了luaforwindows,安装到F:\Lu...

阅读全文

2 条评论

  1. 对于一个生产者,多个消费者的场景,下面这段代码存在一些小问题
    // 通知老板娘赶快做包子
    breadList.notifyAll();

    在实际场景中很可能会导致无效多余的通知。
    比如老板娘要1个小时能才能做好包子。而在程序中Consumer->breadList.notifyAll()本意是想通知老板,但老板还正在忙碌中,所以实际的通知是通知了执行Consumer的其他正在等待的线程,即Consumer通知Consumer,这个通知完全是多余的通知,因为通知Consumer是老板娘线程的职责,而不是Consumer线程的职责。

    你可以在老板娘做包子的时候sleep(999999)测试看看吧。

欢迎留言