Java多线程与并发应用-(3)-传统线程通信技术及生产者消费者模式

生产者/消费者问题是一个经典的线程同步以及通信的案例。该问题描述了两个共享固定大小缓冲区的线程,即所谓的“生产者”和“消费者”在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。要解决该问题,就必须让生产者在缓冲区满时休眠(要么干脆就放弃数据),等到下次消费者消耗缓冲区中的数据的时候,生产者才能被唤醒,开始往缓冲区添加数据。同样,也可以让消费者在缓冲区空时进入休眠,等到生产者往缓冲区添加数据之后,再唤醒消费者,通常采用线程间通信的方法解决该问题。如果解决方法不够完善,则容易出现死锁的情况。出现死锁时,两个线程都会陷入休眠,等待对方唤醒自己。该问题也能被推广到多个生产者和消费者的情形。

例:

写一个线程同步通信的例子,生产者消费者模式,厨师做热狗,售货员卖热狗, 有一个盒子,最多可以放10个热狗,盘子没有热狗时,售货员休息,盘子中有10个热狗时,厨师休息,0<count<10时,2个厨师,2个售货员同时干活

代码如下:

package com.lipeng.waitnotify;

/**
 * 热狗类
 * @author LP
 *
 */
public class HotDog {
	
}

package com.lipeng.waitnotify;

import java.util.ArrayList;
import java.util.List;

/**
 * 盛放热狗的盒子
 * 功能:数据缓存区,容量为10
 * 说明:将不同线程(生产者和消费者)对数据的处理放在同一个类(数据缓存区)中,
 * 这样要同步的方法就可以通过共享数据(此例中的list和count(即list.size))的临界条件来决定执行哪个方法
 * @author LP
 *
 */
public class Box {
	private List<HotDog> list;//存放的热狗
	private int count;//当前存放热狗的数量
	public Box()
	{
		
	}
	public List<HotDog> getList() {
		return list;
	}
	public void setList(List<HotDog> list) {
		this.list = list;
	}
	public int getCount() {
		return count;
	}
	public void setCount(int count) {
		this.count = count;
	}
	/**
	 * 生产
	 * lipeng
	 * 2015-4-13
	 */
	public synchronized void cook(String cookerName)throws Exception
	{
		//当盒子中总数为10时,厨师休息
		while(count==10)
		{
			this.wait();
		}
		HotDog hotDog=new HotDog();
		if(list==null)
		{
			list=new ArrayList<HotDog>();
		}
		list.add(hotDog);
		count++;
		System.out.println(cookerName+" 做了一个热狗,盘子总一共-----------   "+count);
		this.notifyAll();//唤醒其他线程,cook或sale
	}
	/**
	 * 销售
	 * lipeng
	 * 2015-4-13
	 */
	public synchronized void sale(String salerName)throws Exception
	{
		//当盒子中没有热狗时,售货员休息。
		while(count==0)
		{
			this.wait();
		}
		list.remove(0);
		count--;
		System.out.println(salerName+" 卖出去一个热狗,盘子总一共---------------------------------------------------------------   "+count);
		this.notifyAll();//唤醒其他线程,cook或sale
	}
}

package com.lipeng.waitnotify;

/**
 * 厨师类
 * 功能:做热狗,生产者
 * @author LP
 *
 */
public class Cooker implements Runnable{
	private String name;
	private Box box;
	public String getName() {
		return name;
	}
	public Cooker(String name,Box box) {
		this.name=name;
		this.box=box;
	}
	public void setName(String name) {
		this.name = name;
	}
	public Box getBox() {
		return box;
	}
	public void setBox(Box box) {
		this.box = box;
	}
	@Override
	public void run() {
		try {
			for(int i=0;i<200;i++)
			{
				box.cook(name);
			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
}

package com.lipeng.waitnotify;

/**
 * 售货员类
 * 功能:卖热狗,消费者
 * @author LP
 *
 */
public class Salesperson implements Runnable{
	private String name;
	private Box box;
	public String getName() {
		return name;
	}
	public Salesperson(String name,Box box) {
		this.name=name;
		this.box=box;
	}
	public void setName(String name) {
		this.name = name;
	}
	public Box getBox() {
		return box;
	}
	public void setBox(Box box) {
		this.box = box;
	}
	@Override
	public void run() {
		try {
			for(int i=0;i<200;i++)
			{
				box.sale(name);
			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
}

package com.lipeng.waitnotify;


public class Main {
	public static void main(String[] args) {
		Box box=new Box();
		Cooker cooker1=new Cooker("老张",box);
		Cooker cooker2=new Cooker("老王",box);
		Salesperson saler1=new Salesperson("小李", box);
		Salesperson saler2=new Salesperson("小刘", box);
		new Thread(cooker1).start();
		new Thread(cooker2).start();
		new Thread(saler1).start();
		new Thread(saler2).start();
	}
}

运行结果:

说明

1. 需要同步的对象?即共享的数据是Box,那么就要对Box加同步锁,也必须调用box的wait和notify方法。

2. wait和notify方法必须在synchronized方法或块中使用否则,抛出IllegalMonitorStateException异常。

3.要用到共同数据(包括同步锁)的若干个方法应该归在同一个类(数据缓存区)上,同步方法所处理的共享数据是这个类的一个属性,这种设计正好体现了高类聚和程序的健壮性。要同步的方法之间用共享的变量做控制

4. 注意外层循环,即调用实际业务cook和sale的循环,应该在线程的run方法中,而不是在主方法中开启多个线程。这是两种不同的情况。一个是一个线程执行n次,另一个是n个线程执行1次。

5. 为什么要用while而不用if  在wait方法说明中,也推荐使用while,因为在某些特定的情况下,线程有可能被假唤醒,使用while会循环检测更稳妥,下个例子中做详细解释。

6. 当有大于两个线程在执行时最好使用notifyAll唤醒全部阻塞线程。为什么,下篇文章详解。


部分内容转自:http://blog.csdn.net/ghsau/article/details/7433673

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章