Java多线程之使用wait和notify模拟生产者消费者模式

本文讲述如何使用Java多线程的wait和notify方法实现最简单的生产者消费者模式。本文假定读者有一定的Java多线程知识,了解wait, notify, notifyAll, synchronized等关键字的作用,同时理解最简单的生产者消费者模式。

 

创建商品仓库

实现一个简单的商品仓库类,用于存储限定容量的商品。当生产者生产商品时,会将商品存入仓库。当消费者消费商品时,会将商品从仓库中取出。

如下代码示例。创建了一个仓库类Box,其中能容纳的最大商品数量为10,当前仓库中商品数量为0。定义了入库方法add和出库方法方法del,用于生产者和消费者调用。

对于入库方法add, 当入库商品过多时,会调用wait暂停当前入库操作,在while循环中等待下次入库。否则,直接入库,并调用notifyAll,唤醒其它线程继续调用入库或出库。

对于出库方法del,当出库商品不足时,会调用wait暂停当前出库操作,在while循环中等待下次出库。否则,直接出库,并调用notifyAll,唤醒其它线程继续调用入库或出库。

在本示例中,用了一个简单Object对象代表要操作的商品集合。当生产者或消费者调用时,都会对商品mObject进行synchronized,保证商品每次只有一个线程对其进行操作,从而保证商品库存的正常,实现了线程的同步。

public class Box {
	public static final int MAX_NUM = 10; // The box can only store MAX_NUM products.
	private int mCurrentNum = 0;          // Current product number in the box
	private Object mObject = new Object();// The products
	
	/**
	 * Produce products.
	 * @param num the product number to produce
	 */
	public void add(int num) {
		synchronized (mObject) {
			while (true) {
				// Too many products to store into the box
				if (mCurrentNum + num > MAX_NUM) {
					System.out.println("Cannot produce: " + num + ", current: " + mCurrentNum);
					try {
						mObject.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				} else {
					mCurrentNum += num;
					System.out.println("Produce: " + num + ", current: " + mCurrentNum);
					mObject.notifyAll();
					break;
				}
			}
		}
	}
	
	/**
	 * Consume products.
	 * @param num the product number to consume
	 */
	public void del(int num) {
		synchronized (mObject) {
			while (true) {
				// No enough products in the box
				if (mCurrentNum < num) {
					System.out.println("Cannot consume: " + num + ", current: " + mCurrentNum);
					try {
						mObject.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}				
				} else {
					mCurrentNum -= num;
					System.out.println("Consume: " + num + ", current: " + mCurrentNum);
					mObject.notifyAll();
					break;
				}
			}
		}
	}
}

创建线程

现在分别创建生产者和消费者线程,用于对应一个生产者和一个消费者的入库出库操作。由于入库和出库操作必需为同一个商品仓库,因此先创建一个基类线程,参数为仓库对象,而生产者和消费者都需要继承此线程。

基类线程示例代码:

public class MyThread extends Thread {
	protected Box mBox; // Box to store the products
	protected int mNum; // Product number to produce or consume
		
	public MyThread(Box box, int num) {
		mBox = box;
		mNum = num;
	}
}

生产者线程示例代码:

public class ProducerThread extends MyThread {
	public ProducerThread(Box box, int num) {
		super(box, num);
	}

	@Override
	public void run() {
		super.run();
		mBox.add(mNum);
	}
}


消费者线程示例代码:

public class ConsumerThread extends MyThread {
	public ConsumerThread(Box box, int num) {
		super(box, num);
	}

	@Override
	public void run() {
		super.run();
		mBox.del(mNum);
		
	}
}


 

测试

现在分别创建多个生产者消费者线程,用于测试入库出库操作是否正常。

private static void waitNotifyThread1() {
	final Box box = new Box();
	final Thread[] ts = {
		new ProducerThread(box, 1),
		new ProducerThread(box, 3),
		new ProducerThread(box, 9),
		new ProducerThread(box, 5),
		new ProducerThread(box, 2),
		
		new ConsumerThread(box, 5),
		new ConsumerThread(box, 4),
		new ConsumerThread(box, 5),
		new ConsumerThread(box, 6)
	};
	
	for (Thread t:ts) {
		t.start();
	}	
}

运行结果如下:

Produce: 1, current: 1
Produce: 5, current: 6
Consume: 5, current: 1
Produce: 2, current: 3
Produce: 3, current: 6
Cannot produce: 9, current: 6
Consume: 6, current: 0
Cannot consume: 5, current: 0
Cannot consume: 4, current: 0
Produce: 9, current: 9
Consume: 4, current: 5
Consume: 5, current: 0

注意,实际测试时运行结果可能每次不一样,这正是多线程调度的优势所在。另外,如果读者想自己编译测试,请从此处下载源代码http://download.csdn.net/detail/light_vs_shadow/9734000

上一篇:Java创建与结束线程

下一篇:Java多线程之线程池


阅读更多
个人分类: Java
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭