生产者/消费者问题是一个经典的线程同步以及通信的案例。该问题描述了两个共享固定大小缓冲区的线程,即所谓的“生产者”和“消费者”在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。要解决该问题,就必须让生产者在缓冲区满时休眠(要么干脆就放弃数据),等到下次消费者消耗缓冲区中的数据的时候,生产者才能被唤醒,开始往缓冲区添加数据。同样,也可以让消费者在缓冲区空时进入休眠,等到生产者往缓冲区添加数据之后,再唤醒消费者,通常采用线程间通信的方法解决该问题。如果解决方法不够完善,则容易出现死锁的情况。出现死锁时,两个线程都会陷入休眠,等待对方唤醒自己。该问题也能被推广到多个生产者和消费者的情形。
例:
写一个线程同步通信的例子,生产者消费者模式,厨师做热狗,售货员卖热狗, 有一个盒子,最多可以放10个热狗,盘子没有热狗时,售货员休息,盘子中有10个热狗时,厨师休息,0
代码如下:
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 list;//存放的热狗
private int count;//当前存放热狗的数量
public Box()
{
}
public List getList() {
return list;
}
public void setList(List 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();
}
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