生产者消费者问题

生产者和消费者问题是操作系统中的一个经典问题:生产者在生产时,消费者只能等待;消费者在消费时,生产者也只能等待,前提是他们必须共享一个资源。

现在模拟生产者和消费者分别同时向一个果篮装水果和取水果的过程。

限制条件如下:

1)果篮最多装12个水果

2)生产者每次生产的水果数量为随机值,默认1-7,最大值可由生产者自己设定;

3)生产策略为:当生产的水果过量时,必须等待过量的水果全部装入果篮,才继续生产;

4)生产者每生产一次休息一段时间,默认1秒,可以自由设定;

5)消费者每次消费的水果数量为随机值,默认1-7,最大值可由消费者自己设定;

6)消费策略为:当果篮中的水果数量不能满足消费时,消费者进入等待状态,直到果篮水果足够,才开始下一轮消费;

7)消费者消费一次休息一段时间,默认1秒,可以自由设定;


编写代码的思路:

考虑到生产者和消费者得通过果篮对象实现同步互斥,想到类似的收发消息处理方法:即添加一个消息队列,只要在消息队列中设置同步添加和移除消息的方法,这样不但把消息队列独立出来了,对消息的处理也更简单便捷,有助于理解。所以我没把生产者和消费者分别定义成2个单独的线程类,而是针对果篮对象添加了一个同步装入水果和移走水果的方法,生产者只需要把生产的水果通过果篮的同步装入方法装入即可,消费者同理。这样生产者和消费者只需要专注于生产和消费,果篮负责调度生产和消费的水果情况。生产者和消费者的工作效率得到提升。另外定义了一个模拟器类,专门把生产者、消费者放到模拟器里面,模拟器开始执行了(类似工厂开工了),大家都开始干活,模拟器停止了,大家都停止干活。


1、果篮类:封装了同步添加水果和移除水果方法,同时还提供了同步结束添加和移除水果的动作。

代码如下:

/**
 * 果篮类:保存生产者和消费者共享的水果,拥有数量属性 并具有同步添加水果和减少水果方法,同时为了防止生产者或者消费者
 * 进入等待状态而使线程无法终止,还提供了结束生产者和消费者线程的同步方法
 * 
 * @author dobuy
 * @time 2012-5-21
 */
public class FruitHamper
{
	/**
	 * 水果数量
	 */
	private int fruitNum;

	/**
	 * 果篮中最大水果数量
	 */
	private final static int FRUIT_MAX_NUM = 12;

	/**
	 * 剩余水果数量,全局临时变量
	 */
	private int leftFruitNum;

	/**
	 * 果篮停止装入和取出水果标记
	 */
	private boolean isStop;

	/**
	 * 剩余水果等待被装入果篮的消息是否已经提示,默认需要提示
	 */
	private boolean needNotify = true;

	/**
	 * 获取当前的水果数量
	 * 
	 * @return
	 */
	public int getFruitNum()
	{
		return fruitNum;
	}

	/**
	 * 更新当前的水果数量
	 * 
	 * @param fruitNum
	 */
	private void setFruitNum(int fruitNum)
	{
		this.fruitNum = fruitNum;
	}

	/**
	 * 同步向果篮中加入水果方法,如果果篮已满,且生产水果有剩余, 则等到果篮中有位置放下水果后,才开始下一轮生产
	 * 
	 * @param fruitNum 一次生产的水果数量
	 */
	public synchronized void addFruit(int fruitNum)
	{

		// 如果果篮已满,停止添加,生产者线程等待状态
		while (getFruitNum() >= FRUIT_MAX_NUM)
		{
			if (isStop())
			{
				// 生产者停止装入水果消息没有通知时执行
				if (isNeedNotify())
				{
					System.out.println("生产者正在停止装入水果");
					setNeedNotify(false);
				}

				return;
			}

			wait("果篮已满,生产者进入等待状态");
		}

		// 添加后,果篮中实际水果数量
		int currFruitNum = Math.min(FRUIT_MAX_NUM, getFruitNum() + fruitNum);

		// 真正添加的水果数量
		int addFruitNum = currFruitNum - getFruitNum();

		System.out.println("生产者装入" + addFruitNum + "个水果,果篮中共有" + currFruitNum
				+ "个水果");

		// 果篮中水果数量更新
		setFruitNum(currFruitNum);

		// 计算装满果篮剩下的水果
		leftFruitNum = fruitNum - addFruitNum;

		// 当水果有剩余时,继续等待装入果篮
		while (leftFruitNum > 0)
		{
			// 生产者收到停止消息执行
			if (isStop())
			{
				// 生产者停止装入水果消息没有通知时执行
				if (isNeedNotify())
				{
					System.out.println("生产者正在停止装入水果");
					setNeedNotify(false);
				}

				return;
			}

			System.out.println("剩余" + leftFruitNum + "个水果继续等待被装入果篮");

			// 递归调用,剩余水果继续等待被装入果篮
			addFruit(leftFruitNum);

			notifyAll();
		}

		// 激活所有等待线程
		notifyAll();
	}

	/**
	 * 同步从果篮中拿出水果方法,当果篮水果不足时,消费者停止取水果,直到果篮水果充足
	 * 
	 * @param fruitNum
	 */
	public synchronized void removeFruit(int fruitNum)
	{

		// 果篮中水果不足时,消费者线程进入等待状态
		while (fruitNum > getFruitNum())
		{
			// 消费者收到停止消息执行
			if (isStop())
			{
				System.out.println("消费者正在停止取水果");
				return;
			}
			wait("果篮中水果不足,消费者进入等待状态");
		}

		int leftFruitNum = getFruitNum() - fruitNum;

		System.out.println("消费者拿走" + fruitNum + "个水果,果篮中还剩" + leftFruitNum + "个水果");

		// 果篮水果数量更新
		setFruitNum(leftFruitNum);

		// 激活所有等待线程
		notifyAll();
	}

	/**
	 * 果篮停止装入和取出
	 */
	public synchronized void stop()
	{
		// 设置为停止状态
		setStop(true);

		// 唤醒所有等待对象退出
		notifyAll();
	}

	/**
	 * 等待方法
	 * 
	 * @param msg 进入等待状态前的提示消息
	 */
	private void wait(String msg)
	{
		try
		{
			System.out.println(msg);
			wait();
		}
		catch (InterruptedException e)
		{
			System.out.println("exception :" + e.getMessage());
		}
	}

	/**
	 * 获取通知状态
	 * 
	 * @return
	 */
	private boolean isNeedNotify()
	{
		return needNotify;
	}

	/**
	 * 设置通知状态
	 * 
	 * @param needNotify
	 */
	private void setNeedNotify(boolean needNotify)
	{
		this.needNotify = needNotify;
	}

	/**
	 * 果篮是否停止装入/取出水果
	 * 
	 * @return
	 */
	public boolean isStop()
	{
		return isStop;
	}

	/**
	 * 设置果篮是否停止装入/取出水果
	 * 
	 * @param isStop
	 */
	public void setStop(boolean isStop)
	{
		this.isStop = isStop;
	}
}
2、生产者类:

import java.util.Random;

/**
 * 水果生产者类,负责把水果装入果篮
 * 
 * @author dobuy
 * @time 2012-5-21
 */
public class FruitProducer
{
	/**
	 * 生产者装入水果的目标果篮
	 */
	private FruitHamper fruitHamper;

	/**
	 * 生产者一次生产水果的上限
	 */
	private int produceMaxNum = 7;

	/**
	 * 定义一个随机数表示每次生产的水果数量
	 */
	private Random random = new Random();

	/**
	 * 构造方法,隐藏创建未指定果篮的生产者
	 */
	@SuppressWarnings("unused")
	private FruitProducer()
	{
	}

	/**
	 * 构造方法,创建带果篮的生产者
	 * 
	 * @param fruitHamper
	 */
	public FruitProducer(FruitHamper fruitHamper)
	{
		this.fruitHamper = fruitHamper;
	}

	/**
	 * 获取生产者对应的果篮对象
	 * 
	 * @return
	 */
	public FruitHamper getFruitHamper()
	{
		return fruitHamper;
	}

	/**
	 * 获取生产者生产水果的上限
	 * 
	 * @return
	 */
	public int getProduceMaxNum()
	{
		return produceMaxNum;
	}

	/**
	 * 设置生产者一次生产水果的上限
	 * 
	 * @param produceMaxNum
	 */
	public void setProduceMaxNum(int produceMaxNum)
	{
		this.produceMaxNum = produceMaxNum;
	}

	/**
	 * 生产者生产一次水果
	 */
	public void produce()
	{
		// 每次生产的水果数量
		int fruitNum = random.nextInt(getProduceMaxNum() - 1) + 1;

		System.out.println("生产者生产了" + fruitNum + "个水果,正准备装入果篮");

		// 向果篮中添加1到上限值之间的水果数量
		getFruitHamper().addFruit(fruitNum);
	}
}
3、消费者类:

import java.util.Random;

/**
 * 消费者类,专门负责从果篮中取出水果
 * 
 * @author dobuy
 * @time 2012-5-21
 * 
 */
public class FruitConsumer
{
	/**
	 * 消费者指定的果篮对象
	 */
	private FruitHamper fruitHamper;

	/**
	 * 定义一个随机数表示每次生产的水果数量
	 */
	private Random random = new Random();

	/**
	 * 消费者每次取出水果范围的上限,设置默认值
	 */
	private int consumeMaxNum = 7;

	/**
	 * 构造方法:隐藏创建不带果篮的消费者对象
	 */
	@SuppressWarnings("unused")
	private FruitConsumer()
	{
	}

	/**
	 * 构造方法:创建带果篮的消费者对象
	 * 
	 * @param fruitHamper
	 */
	public FruitConsumer(FruitHamper fruitHamper)
	{
		this.fruitHamper = fruitHamper;
	}

	/**
	 * 获取消费者指定的果篮对象
	 * 
	 * @return
	 */
	public FruitHamper getFruitHamper()
	{
		return fruitHamper;
	}

	/**
	 * 消费者从果篮中取一次水果
	 */
	public void consume()
	{
		// 每次消费的水果数量
		int fruitNum = random.nextInt(getConsumeMaxNum() - 1) + 1;

		System.out.println("消费者计划从果篮取" + fruitNum + "个水果");

		// 从果篮中移除1-上限值之间的水果数
		getFruitHamper().removeFruit(fruitNum);
	}

	/**
	 * 获取消费者每次消费的最大数量
	 * 
	 * @return
	 */
	public int getConsumeMaxNum()
	{
		return consumeMaxNum;
	}

	/**
	 * 设置消费者每次消费子弹的最大数量
	 * 
	 * @param consumeMaxNum 消费子弹的上限
	 */
	public void setConsumeMaxNum(int consumeMaxNum)
	{
		this.consumeMaxNum = consumeMaxNum;
	}
}
4、模拟器类:在模拟器中加载生产者和消费者对象,启动模拟器后会自动启动生产者和消费者线程,模拟器停止时,生产者和消费者线程也停止。

/**
 * 模拟器类,用来模拟生产者消费者模型
 * 
 * @author dobuy
 * @time 2012-5-21
 */
public class Simulator
{
	/**
	 * 定义生产者对象
	 */
	private FruitProducer fruitProducer;
	/**
	 * 定义消费者对象
	 */
	private FruitConsumer fruitConsumer;

	/**
	 * 定义模拟器运行状态,默认未启动
	 */
	private boolean isStop = true;

	/**
	 * 获取模拟器运行状态
	 * 
	 * @return
	 */
	private boolean isStop()
	{
		return isStop;
	}

	/**
	 * 设置模拟器运行状态
	 * 
	 * @param isStop
	 */
	private void setStop(boolean isStop)
	{
		this.isStop = isStop;
	}

	/**
	 * 获取模拟器中的生产者对象
	 * 
	 * @return
	 */
	public FruitProducer getFruitProducer()
	{
		return fruitProducer;
	}

	/**
	 * 设置模拟器中的生产者对象
	 * 
	 * @param bulletsProducer
	 */
	public void setFruitProducer(FruitProducer fruitProducer)
	{
		this.fruitProducer = fruitProducer;
	}

	/**
	 * 获取模拟器中的消费者对象
	 * 
	 * @return
	 */
	public FruitConsumer getFruitConsumer()
	{
		return fruitConsumer;
	}

	/**
	 * 设置模拟器中的消费者对象
	 * 
	 * @param bulletsConsumer
	 */
	public void setFruitConsumer(FruitConsumer fruitConsumer)
	{
		this.fruitConsumer = fruitConsumer;
	}

	/**
	 * 启动模拟器
	 */
	public void start()
	{
		// 生产者/消费者未指定时,直接提示异常,退出
		if (null == getFruitProducer() || null == getFruitConsumer())
		{
			System.err.println("生产者/消费者对象未指定,模拟器启动失败");
			return;
		}

		// 生产者/消费者的果篮对象未指定,直接提示异常,退出
		if (null == getFruitProducer().getFruitHamper()
				|| null == getFruitConsumer().getFruitHamper())
		{
			System.err.println("生产者/消费者对应的果篮对象未指定,模拟器启动失败");
			return;
		}

		// 生产者和消费者指定的果篮对象不同,无法模拟,退出
		if (!getFruitProducer().getFruitHamper().equals(
				getFruitConsumer().getFruitHamper()))
		{
			System.err.println("生产者、消费者用的果篮不同,无法模拟");
			return;
		}

		// 生产者/消费者设置生产/消费的最大水果数量不合法
		if (getFruitProducer().getProduceMaxNum() < 0
				|| getFruitConsumer().getConsumeMaxNum() < 0)
		{
			System.err.println("生产者/消费者设置生产/消费的最大水果数量不合法,无法模拟");
			return;
		}

		// 设置模拟器为运行状态
		setStop(false);

		new Thread(produceThread).start();
		new Thread(consumeThread).start();
	}

	public void stop()
	{
		// 只在模拟器正常开启后执行关闭操作
		if (!isStop())
		{
			setStop(true);

			// 关闭果篮的装入/取出水果操作
			getFruitConsumer().getFruitHamper().stop();

			System.out.println("模拟器已经停止");
		}
	}

	/**
	 * 定义生产线程
	 */
	private Runnable produceThread = new Runnable()
	{
		@Override
		public void run()
		{
			while (!isStop())
			{
				fruitProducer.produce();
				// 生产一次后休息一段时间
				sleepTime(1000);
			}

			System.out.println("生产者已经停止");
		}
	};

	/**
	 * 定义消费线程
	 */
	private Runnable consumeThread = new Runnable()
	{
		@Override
		public void run()
		{
			while (!isStop())
			{
				fruitConsumer.consume();
				// 消费一次后休息一段时间
				sleepTime(1000);
			}

			System.out.println("消费者已经停止");
		}
	};

	/**
	 * 设置线程执行休眠实现
	 * 
	 * @param time 毫秒
	 */
	public void sleepTime(long time)
	{
		try
		{
			Thread.sleep(time);
		}
		catch (InterruptedException e)
		{
			e.printStackTrace();
		}
	}

	public static void main(String[] args)
	{
		// 创建模拟器
		Simulator simulator = new Simulator();

		// 创建果篮
		FruitHamper fruitHamper = new FruitHamper();

		// 创建生产者
		FruitProducer producer = new FruitProducer(fruitHamper);

		// 创建消费者
		FruitConsumer consumer = new FruitConsumer(fruitHamper);

		// 生产者设置生产一次水果的上限,可以不设
		producer.setProduceMaxNum(8);

		// 消费者设置消费一次水果的上限,可以不设
		consumer.setConsumeMaxNum(5);

		// 加载生产者对象
		simulator.setFruitProducer(producer);

		// 加载消费者对象
		simulator.setFruitConsumer(consumer);

		// 模拟器启动
		simulator.start();

		// 设置模拟器运行时间
		simulator.sleepTime(1000 * 10);

		// 模拟器停止
		simulator.stop();
	}
}
写在最后的话:

1)发现想把自己的想法表达清楚不是件容易的事,我尽力了;

2)控制台打印的输出结果并不完全是按照理想顺序来的,因为在生产者生产一次和消费者消费一次时都设置了休眠时间,尤其是休眠时间长度还一样,这个调整后,输出就会更合理点了;

3)本来是用Junit进行测试的,但是Junit测试时只要主线程结束了,整个程序都结束,但是生产者消费者线程并没有完全执行完,所以最后改到main里执行了;

4)如果有错误或者不合理的地方,还请多指点,感激不尽!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值