使用wait/notify实现生产者/消费者模式遇到的问题,以及求详解notify和notifyAll

偏安zzcoder 2017-10-30 06:42:26
场景描述:
生产者/消费者模式是进程与线程学习中相当经典的问题,在本文中场景设置如下:
有一个仓库(Repository),有若干个生产者(Producer)和若干个消费者(Consumer),生产者可以生产产品(Procuct)并存入(push)仓库,消费者可以从仓库取出(pop)并消费产品。
我希望通过使用synchronize锁机制与wait()/notifyAll()或notify()方法实现,代码如下。但实际测试中发现,当使用notify方法时,线程仅会通知最近使用的线程,时间一长就会变成仅有两个线程在实际运行。而使用notifyAll()方法时,仓库实际产品最大数量仅是创建的生产者的最大数量,却不能达到自己预先定义的最大数量,且线程执行顺序似乎并非随机的而是按照某一固定的顺序去执行。此处暂未想通。目测估计是我的线程唤醒方法调用的位置或者调用的方式有问题,正在努力解决中。求各位大佬帮忙解答。

代码如下:


import java.util.ArrayList;

/**
* 多生产者多消费者单仓库的生产者消费者模式
* @author breezefaith
*
*/
class Product{
private int productId;

public Product(int productId) {
super();
this.productId = productId;
}

@Override
public String toString() {
return "Product["+productId+"]";
}

}

class Repository{
private int maxSize;
private ArrayList<Product> list;

public Repository(int maxSize) {
super();
this.maxSize = maxSize;
list=new ArrayList<>(maxSize);
}
public void setMaxSize(int maxSize) {
if(maxSize<=list.size()) {
System.err.println("New size is smaller than the original.");
return;
}
ArrayList<Product> newList=new ArrayList<>(maxSize);
newList.addAll(list);
list=newList;
}
public int getMaxSize() {
return maxSize;
}
public int getCurrentSize() {
return list.size();
}
public void push(Product product) {
list.add(product);
}
public Product pop() {
return list.remove(0);
}
}

class Producer extends Thread{
private Repository repository;
private static int productId;
public Producer(Repository repository) {
super();
this.repository = repository;
}

public Product produce() {
Product product=new Product(productId++);
System.out.println("生产者 "+Thread.currentThread().getName()+" 生产了产品 "+product+",剩余:"+(repository.getCurrentSize()+1));
return product;
}

@Override
public void run() {
synchronized (repository) {
try {
while(true) {
Thread.sleep(1000);
if(repository.getCurrentSize()<repository.getMaxSize()) {
repository.push(produce());
}else {
System.err.println("Producer "+Thread.currentThread().getName()+" want to produce a product, but repository is full.");
}
repository.notifyAll();
repository.wait();
}
}catch(InterruptedException e) {
e.printStackTrace();
}
}
}

}

class Consumer extends Thread{
private Repository repository;

public Consumer(Repository repository) {
super();
this.repository = repository;
}

public Product consume() {
Product product=repository.pop();
System.out.println("消费者 "+Thread.currentThread().getName()+" 消费了产品 "+product+",剩余:"+repository.getCurrentSize());
return product;
}

@Override
public void run() {
synchronized (repository) {
try {
while(true) {
Thread.sleep(1000);
if(repository.getCurrentSize()>0) {
consume();
}else {
System.err.println("Consumer "+Thread.currentThread().getName()+" consume a product, but repository is empty.");
}
repository.notifyAll();
repository.wait();
}
}catch(InterruptedException e) {
e.printStackTrace();
}
}
}
}

public class Run {

public static void main(String[] args) {
Repository repository=new Repository(3);
Producer p1=new Producer(repository),
p2=new Producer(repository),
p3=new Producer(repository);
p1.setName("p1");
p2.setName("p2");
p3.setName("p3");

Consumer c1=new Consumer(repository),
c2=new Consumer(repository);
c1.setName("c1");
c2.setName("c2");

p1.start();
p2.start();
p3.start();
c1.start();
c2.start();
}

}


实验结果之一:
生产者 p1 生产了产品 Product[0],剩余:1
消费者 c1 消费了产品 Product[0],剩余:0
Consumer c2 consume a product, but repository is empty.
生产者 p3 生产了产品 Product[1],剩余:1
生产者 p2 生产了产品 Product[2],剩余:2
生产者 p3 生产了产品 Product[3],剩余:3
消费者 c2 消费了产品 Product[1],剩余:2
消费者 c1 消费了产品 Product[2],剩余:1
生产者 p1 生产了产品 Product[4],剩余:2
消费者 c1 消费了产品 Product[3],剩余:1
消费者 c2 消费了产品 Product[4],剩余:0
生产者 p3 生产了产品 Product[5],剩余:1
生产者 p2 生产了产品 Product[6],剩余:2
生产者 p3 生产了产品 Product[7],剩余:3
消费者 c2 消费了产品 Product[5],剩余:2
消费者 c1 消费了产品 Product[6],剩余:1
生产者 p1 生产了产品 Product[8],剩余:2
...全文
773 6 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
6 条回复
切换为时间正序
请发表友善的回复…
发表回复
dgqjava 2017-10-31
  • 打赏
  • 举报
回复
引用 4 楼 kingslave1 的回复:
[quote=引用 3 楼 dgqjava 的回复:] [quote=引用 2 楼 kingslave1 的回复:] [quote=引用 1 楼 dgqjava 的回复:] 代码并没有什么大问题
我觉得在notify和wait的使用细节上可能出问题了,感觉得理解一下这两个方法的具体实现[/quote] 没发现你的输出结果有你说的问题啊[/quote] 你自己copy这段代码,然后new Repository时把3改成10就会发现问题了,我贴的代码刚好仓库最大容量也为3,所以你看不出来。另外运行时间长了以后就会发现他执行顺序上的规律,你看我上面的结果是不是基本稳定在“3个生产者->2个消费者->1个生产者->2个消费者”,所以他的实际容量永远不会超过生产者的数量3。这让我很郁闷[/quote] 是这样的, 由于同步代码的存在 因此同一时间只有一个生产者或者消费者在工作 当前工作的线程notifyall之后所有其他线程从等待池进入到锁池等待获取锁 当前工作线程wait之后 锁池里的线程开始获取锁并得到执行机会 这个获取锁的顺序是和底层实现相关的 目前的实现看起来这个池就相当于是一个队列 因此造成的结果就是一个生产者生成结束之后 其他被notify的所有线程会依次得到一次执行机会 因此产品数量不会超过生产者的数量
偏安zzcoder 2017-10-31
  • 打赏
  • 举报
回复
引用 3 楼 dgqjava 的回复:
[quote=引用 2 楼 kingslave1 的回复:] [quote=引用 1 楼 dgqjava 的回复:] 代码并没有什么大问题
我觉得在notify和wait的使用细节上可能出问题了,感觉得理解一下这两个方法的具体实现[/quote] 没发现你的输出结果有你说的问题啊[/quote] 你自己copy这段代码,然后new Repository时把3改成10就会发现问题了,我贴的代码刚好仓库最大容量也为3,所以你看不出来。另外运行时间长了以后就会发现他执行顺序上的规律,你看我上面的结果是不是基本稳定在“3个生产者->2个消费者->1个生产者->2个消费者”,所以他的实际容量永远不会超过生产者的数量3。这让我很郁闷
dgqjava 2017-10-31
  • 打赏
  • 举报
回复
引用 2 楼 kingslave1 的回复:
[quote=引用 1 楼 dgqjava 的回复:] 代码并没有什么大问题
我觉得在notify和wait的使用细节上可能出问题了,感觉得理解一下这两个方法的具体实现[/quote] 没发现你的输出结果有你说的问题啊
偏安zzcoder 2017-10-31
  • 打赏
  • 举报
回复
引用 1 楼 dgqjava 的回复:
代码并没有什么大问题
我觉得在notify和wait的使用细节上可能出问题了,感觉得理解一下这两个方法的具体实现
dgqjava 2017-10-31
  • 打赏
  • 举报
回复
代码并没有什么大问题
偏安zzcoder 2017-10-31
  • 打赏
  • 举报
回复
引用 5 楼 dgqjava的回复:
[quote=引用 4 楼 kingslave1 的回复:] [quote=引用 3 楼 dgqjava 的回复:] [quote=引用 2 楼 kingslave1 的回复:] [quote=引用 1 楼 dgqjava 的回复:] 代码并没有什么大问题
我觉得在notify和wait的使用细节上可能出问题了,感觉得理解一下这两个方法的具体实现[/quote] 没发现你的输出结果有你说的问题啊[/quote] 你自己copy这段代码,然后new Repository时把3改成10就会发现问题了,我贴的代码刚好仓库最大容量也为3,所以你看不出来。另外运行时间长了以后就会发现他执行顺序上的规律,你看我上面的结果是不是基本稳定在“3个生产者->2个消费者->1个生产者->2个消费者”,所以他的实际容量永远不会超过生产者的数量3。这让我很郁闷[/quote] 是这样的, 由于同步代码的存在 因此同一时间只有一个生产者或者消费者在工作 当前工作的线程notifyall之后所有其他线程从等待池进入到锁池等待获取锁 当前工作线程wait之后 锁池里的线程开始获取锁并得到执行机会 这个获取锁的顺序是和底层实现相关的 目前的实现看起来这个池就相当于是一个队列 因此造成的结果就是一个生产者生成结束之后 其他被notify的所有线程会依次得到一次执行机会 因此产品数量不会超过生产者的数量[/quote] 感谢,我也觉得是底层实现上的问题,但是有点想不通,现在多少有点明白了

62,635

社区成员

发帖
与我相关
我的任务
社区描述
Java 2 Standard Edition
社区管理员
  • Java SE
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧