上一篇博客(http://blog.csdn.net/u011991249/article/details/52589010)中记录了单线程模式下的生产者消费者模式的实现,但是如果变成多线程模式,那种实现方式就会产生问题。
测试类做一些改动(整个工程目录结构与上一篇单线程生产者消费者模式一致):
代码如下:
public class ProductAndCustom {
public static void main(String[] args) {
//创造一个资源类
Resource resource = new Resource();
//启动生产线程来生产
new Thread(new Productor(resource)).start();
new Thread(new Productor(resource)).start();
//启动消费线程来消费
new Thread(new Customer(resource)).start();
new Thread(new Customer(resource)).start();
}
}
运行程序会产生如下日志信息(仅截取一部分):
发现有些产品消费了N次,当然也可能会出现连续生产了多次的情况。
现在的情形大概是下图这样:
T0、T1代表生产者线程,T2、T3代表消费者线程。
再结合Resource类中的生产方法为例进行分析:
//生产方法
public synchronized void set(String name){
//如果有商品,那么就等待一下
if(hasGoods){
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
this.name = name + "_" + id;
id++;
System.out.println("生产线程名称"+Thread.currentThread().getName()+"已生产商品名称是"+this.name);
hasGoods = true;
//本意是通知消费线程来消费
notify();
}
分析:首先T0线程进行生产方法进行生产,生产完成,hasGoods被置成true,并且通知其他线程(T0、T1、T2、T3),假设通知了T1线程,那么T1线程运行set(生产)方法,进入wait状态,如果再通知T0状态,则T0线程也进入wait状态;如果通知T2或者T3线程,那么运行out(消费)方法,此时hasGoods被置成false,并且通知其他线程(T0、T1、T2、T3),如果此时通知了T1和T0,那么因为T1和T0线程不会再走if判断直接进行生产,那么就会出现生产N次的情况(消费N次的情况类似)。
解决这种问题的办法就是将if改成while。
修改之后Resource.java 文件是:
public class Resource {
//商品名称
private String name;
//商品编号
private int id=1;
//是否有商品
private boolean hasGoods = false;
//生产方法
public synchronized void set(String name){
//如果有商品,那么就等待一下
while(hasGoods){
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
this.name = name + "_" + id;
id++;
System.out.println("生产线程名称"+Thread.currentThread().getName()+"已生产商品名称是"+this.name);
hasGoods = true;
//本意是通知消费线程来消费
notify();
}
//消费方法
public synchronized void out(){
//如果没有商品,那么就等待一下
while(!hasGoods){
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("消费线程名称"+Thread.currentThread().getName()+"已消费消费商品名称是"+this.name);
hasGoods = false;
//本意是通知生产线程来生产
notify();
}
}
但是我们这样修改完成之后,再次运行,会发现程序出现死锁的情况。
我的理解是:
紧接上一个错误分析,假设正常运行一段时间之后,T0、T1进行wait状态,然后通知T2,T2线程后自行结束之后再通知T3,T3结束之后再通知T2,此时T2和T3也进入wait状态,此时四个线程全部进行wait状态,出现死锁。
解决方法是:唤醒时应该全部唤醒(保证唤醒所有生产者消费者线程,防止只是唤醒某一类(生产或者消费)线程引起死锁)。
修改之后Resource.java 文件是:
public class Resource {
//商品名称
private String name;
//商品编号
private int id=1;
//是否有商品
private boolean hasGoods = false;
//生产方法
public synchronized void set(String name){
//如果有商品,那么就等待一下
while(hasGoods){
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
this.name = name + "_" + id;
id++;
System.out.println("生产线程名称"+Thread.currentThread().getName()+"已生产商品名称是"+this.name);
hasGoods = true;
//本意是通知消费线程来消费
notifyAll();
}
//消费方法
public synchronized void out(){
//如果没有商品,那么就等待一下
while(!hasGoods){
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("消费线程名称"+Thread.currentThread().getName()+"已消费消费商品名称是"+this.name);
hasGoods = false;
//本意是通知生产线程来生产
notifyAll();
}
}
这样就能解决多线程中出现死锁的情况。
但是这种解决依旧会存在性能方面的问题。(可以采用lock方法进行调优,下一篇博客会进行共享)