Java 多线程学习之生产者消费者模型:一个较完善的实现

生产者、消费者模型是学习多线程的时候的一个很好的练习模型。该问题专业的说法应为:有限缓冲问题。该问题描述了两个共享固定大小缓冲区的线程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。

生产者的作用是生成一定量的数据放到缓冲区中,然后重复此过程。而消费者的作用则是消耗这些数据。问题的关键是要保证生产者不会在缓冲区满时加入数据,消费者也不会再缓冲区空时消耗数据。

解决问题的方法可以采用线程编程,也可以不采用线程编程。不采用的话就是简单的做满或空状态的判断,不满足条件的时候则放弃数据。当然这样的实现意义不大。如果要采用线程编程,则在缓冲区满时生产者休眠,等到消费者消耗缓冲区中的数据的时候再唤醒生产者。同理,在缓冲区空时让消费者休眠,当生产者往缓冲区添加数据之后再将其唤醒。

-------------------------------------------------------------------------------------------------------------------------------

以下的实现方案是自己学习了java线程知识之后写的一个初级解决方案,用到了wait(),notify()等基础线程通信方法。

称其实较为完善的方法,因为该方案可以实现单个生产者与单个消费者,也可以适用于多个生产者与多个消费者。

不仅如此,因为该方案中可以制定每个生产者生产任务,和每个消费者的消费任务,如果两者总和相等,那么非常容易解决。但是如果两者总和不等,比如生产者的总任务有20个,消费者的总任务有30个,那么生产者全部生产完之后,消费者将会无限期死等待。后来我加入了一些检测机制来解决死等待,如果一方任务已经完成,另外一方即将进入wait()的时候检测到了对方任务全部完成,则不再wait(),而是skip wait,也直接结束任务。

该实现的不足之处在于设计模式上的问题,因为没有好好地打草稿设计,可能一些方法的实现违背了OO的一些基本原则,留待以后改进。同时维基上提到了信号灯算法,管程算法等成熟的算法,以及多线程中condition、重入锁等特性都没有深入研究过,这些可以留待以后再实现。

--------------------------------------------------------------------------------------------------------------------------------

因为内容过多,只能简述一下实现思路:

为了比较好的模拟实际,我写了Consumer消费者类、Producer生产者类、Product产品类、Warehouse仓库类,已经对于单C单P,单C多P,多C单P,多C多P各写了一个测试类,结果运行良好。

其中几个类简述如下,具体看代码吧,内容太多了:

Consumer:模拟消费者,可以有多个实例,每个具有一个名字,每个的消费任务量可以指定,然后有一个类静态变量标明总任务数。

public class Consumer implements Runnable{
	private static int totalTaskNumber=0;
	private int myTaskNumber=0;
	private static int totalTaskRemain=0;
	private int myTaskRemain=0;
	private String name;
	private Warehouse wh;
	private static int totalConsumed=0; //The total number of product produced yet.
	private int thisOneConsumed=0; //The number of product produced by this producer.
	private boolean needContinue=true;
	
	public Consumer(String name, Warehouse wh, int taskNumber){
		this.name=name;
		this.wh=wh;
		myTaskNumber=taskNumber;
		myTaskRemain=taskNumber;
		totalTaskNumber+=taskNumber;
		totalTaskRemain+=taskNumber;
	}
	
	public void consume(){
//		System.out.println("Im consumer here!");
		Product popedProduct=wh.pop(this);
		if(popedProduct==null) needContinue=false;
	}
	
	public void run(){
		for(int i=0;i<myTaskNumber&&needContinue;i++){
			consume();
		}
	}
	
	public String toString(){
		String str="";
		str+="Totally comsumed "+totalConsumed+" products. Consumer "+name+" consumed "+thisOneConsumed+" products";
		return str;
	}
	
	public String getName(){
		return name;
	}
	
	public void testFinish(){
		if(myTaskRemain==0){
			System.out.println("Consumer "+name+" finished his task!\n");
			
			if(totalTaskRemain==0){
				System.out.println("All consumers' task are finished!\n");
			}
		}
	}
	
	public int getTotalTaskRemain(){
		return totalTaskRemain;
	}
	
	public void decMyTaskRemain(){
		myTaskRemain--;
	}
	
	public void decTotalTaskRemain(){
		totalTaskRemain--;
	}
	
	public void incTotalConsumed(){
		totalConsumed++;
	}
	
	public void incThisOneConsumed(){
		thisOneConsumed++;
	}
	
}


Producer:模拟生产者,与消费者差不多。

public class Producer implements Runnable{
	private static int totalTaskNumber=0;
	private int myTaskNumber=0;
	private static int totalTaskRemain=0;
	private int myTaskRemain=0;
	private String name;
	private Warehouse wh;
	private static int totalProduced=0; //The total number of product produced yet.
	private int thisOneProduced=0; //The number of product produced by this producer.
	private boolean needContinue=true;
	
	public Producer(String name, Warehouse wh, int taskNumber){
		this.name=name;
		this.wh=wh;
		myTaskNumber=taskNumber;
		myTaskRemain=taskNumber;
		totalTaskNumber+=myTaskNumber;
		totalTaskRemain+=myTaskNumber;
	}
	
	public void produce(){
		Product toProduce=new Product(++Product.totalID);
		needContinue=wh.push(toProduce,this);
	}
	
	public void run(){
		for(int i=0;i<myTaskNumber&&needContinue;i++){
			produce();
		}			
	}
	
	public String toString(){
		String str="";
		str+="Totally produced "+totalProduced+" products. Producer "+name+" produced "+thisOneProduced+" products";
		return str;
	}
	
	public String getName(){
		return name;
	}
	
	public void testFinish(){
		if(myTaskRemain==0){
			System.out.println("Producer "+name+" finished his task!\n");
			
			if(totalTaskRemain==0){
				System.out.println("All producers' taks are finished!\n");
			}
		}
	}
	
	public int getTotalTaskRemain(){
		return totalTaskRemain;
	}
	
	public void decMyTaskRemain(){
		myTaskRemain--;
	}
	
	public void decTotalTaskRemain(){
		totalTaskRemain--;
	}
	
	public void incTotalProduced(){
		totalProduced++;
	}
	
	public void incThisOneProduced(){
		thisOneProduced++;
	}
}


Product:模拟生产的产品。现在这个类比较简单,只有一个属性ID编号,就是每次生产出来一个产品的时候,给它依次编个号。之所以写这个类来模拟生产的产品,而不是简单地推入数据,是因为以后可以方便地扩展该类,潜在的实用性还是很大的。

public class Product {
	public static int totalID=0;
	private int ID;
	
	public Product(int ID){
		this.ID=ID;
	}
	
	public int getID(){
		return ID;
	}
}


Warehouse:模拟仓库,也就是所谓的多线程共享的数据缓冲区,大部分设计心血都在这个类里。结构是用堆栈实现的,最重要的操作是push,pop方法,前者只能由生产者的实例调用,后者只能由消费者的实例调用。其他还有一些isEmpty,isFull等等的辅助方法,详情可以参见我博客里关于堆栈实现的一篇日志。

现在讲一下Warehouse里的一些细节,是实现的关键。当仓库满的时候,isFull方法返回true,这个时候再想调用push方法的生产者,他们想push的产品已经构造好了,但是在推入仓库的这一步将被转为wait(),直到有消费者来notifyAll()。同理,如果仓库满的时候,消费者再想pop()的话也将转入等待,直到被刚刚完成生产工作的生产者唤醒。进一步的,每一次生产任务完成后都要notifyAll,通知在等待的消费者可以消费了,每一次消费任务完成后也要用notifyAll来通知生产者仓库不再爆仓,可以来推入产品了。

接下来谈一下任务不同步的问题。每一个生产者或者消费者在被创建实例的时候都可以指派生产或者消费任务,如果生产者的总任务和与消费者的总任务和不相等的话,就会造成一方的永久等待。为了解决这个问题,加入生产者和消费者的类静态变量表明总任务,每生产或消费一个,静态变量减一,归零的时候表明这一方的任务全部完成了。故每一方在转入wait()方法的时候,先检查对方的这个变量,如果对方的这个变量是0,任务已经全部完成,那么你再等待也没意义了,只会陷入永久等待,因此这一方也就直接结束了。

如果是生产者先结束的话,那么消费者在将仓库里的产品消费光以后,即使还有消费任务也不管了,直接结束。

如果是消费者先结束的话,那么生产者其实会继续生产,将仓库里铺满爆仓,之后原本即将转入wait()状态,但是即使转入了也没有消费者来消费了,那么也就直接结束啦!

public class Warehouse {
	private final int capacity=10;
	private Product[] storage;
	private int nextPos=0;
	boolean allCsmFinished=false;
	boolean allPdcFinished=false;
	
	public Warehouse(){
		storage=new Product[10];
	}

	//This method is only invoked by producer
	public synchronized boolean push(Product toPush, Producer invoker){
		//The use  of while is very important. Using if() statement will cause exception in M_P_M_C model!.
		while(isFull()){ 
			try {
				if(allCsmFinished){
					System.out.println("ACF! "+invoker.getName()+" skips waiting!\n");
					return false;
				}
				
				System.out.println(invoker.getName()+" waits!\n");
				wait();	//The invoker of wait() method is acquiescently the owner of this synchronized lock.
			} catch (InterruptedException e) {
				e.printStackTrace();
			} 
		}
		storage[nextPos]=toPush;
		nextPos++;
		
		System.out.println("Produced! ID "+toPush.getID()+" "+toString());
		
		invoker.incThisOneProduced();
		invoker.incTotalProduced();
		System.out.println(invoker.toString()+"\n");
		invoker.decMyTaskRemain();
		invoker.decTotalTaskRemain();
		invoker.testFinish();
		
		if(invoker.getTotalTaskRemain()==0) setAllPdcFinished(true);
//		System.out.println("Producer notifies all!\n");
		//It's safe to notifyAll() at this moment since nextPos is already changed.
		notifyAll();
		return true;
	}

	//This method is only invoked by consumer
	public synchronized Product pop(Consumer invoker){
		//The use  of while is very important. Using if() statement will cause exception in M_P_M_C model!.
		while(isEmpty()){
			try{
				if(allPdcFinished){
					System.out.println("APF! "+invoker.getName()+" skips waiting!\n");
					return null;
				}
				
				System.out.println(invoker.getName()+" waits!\n");
//				System.out.println(Test_SingleP_MultiC.CT1.isAlive());
//				System.out.println(Test_SingleP_MultiC.CT2.isAlive());
//				System.out.println(Test_SingleP_MultiC.PT1.isAlive());
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		
		int idxToReturn=--nextPos;
		
		System.out.println("Consumed! ID "+storage[idxToReturn].getID()+" "+toString());
		
		invoker.incThisOneConsumed();
		invoker.incTotalConsumed();
		System.out.println(invoker.toString()+"\n");
		invoker.decMyTaskRemain();
		invoker.decTotalTaskRemain();
		invoker.testFinish();
		if(invoker.getTotalTaskRemain()==0) setAllCsmFinished(true);
		//NotifyAll() at this time is secure, because nextPos is changed, and return index is saved in another variabl and thus unchanged.
		notifyAll();
		return storage[idxToReturn];
	}
	
	public boolean isEmpty(){
		return nextPos==0;
	}
	
	public boolean isFull(){
		return nextPos==capacity;
	}
	
	public int inventory(){
		return nextPos;
	}
	
	public void setAllCsmFinished(boolean toSet){
		allCsmFinished=toSet;
	}
	
	public void setAllPdcFinished(boolean toSet){
		allPdcFinished=toSet;
	}
	
	public String toString(){
		String str="";
		str+="[Warehouse status: Inventory="+inventory()+" Products:";
		for(int i=0;i<nextPos;i++)
			str+=" "+storage[i].getID();
		str+=" ]";
		return str;
	}
}

另外附上四个测试类:

1.单生产者,单消费者。

public class Test_SingleP_SingleC {
	public static void main(String[] args){
		Warehouse wh=new Warehouse();
		Thread PT1=new Thread(new Producer("P_one",wh,50),"ProducerThread one");
		Thread CT1=new Thread(new Consumer("C_one",wh,50),"ConsumerrThread one");
//		PT1.setPriority(Thread.MIN_PRIORITY);
//		CT1.setPriority(Thread.MAX_PRIORITY);
		PT1.start();
		CT1.start();		
	}
}


2.单生产者,多消费者。

public class Test_SingleP_MultiC {
	public static void main(String[] args){
		Warehouse wh=new Warehouse();
		Thread PT1=new Thread(new Producer("P_one",wh,50),"ProducerThread one");
		Thread CT1=new Thread(new Consumer("C_one",wh,1),"ConsumerrThread one");
		Thread CT2=new Thread(new Consumer("C_two",wh,1),"ConsumerrThread two");
		
		
//		PT1.setPriority(Thread.MIN_PRIORITY);
//		CT1.setPriority(Thread.MAX_PRIORITY);
		PT1.start();
		CT1.start();
		CT2.start();
	}
}


3.多生产者,单消费者。

public class Test_MultiP_SingleC {
	public static void main(String[] args){
		Warehouse wh=new Warehouse();
		Thread PT1=new Thread(new Producer("P_one",wh,20),"ProducerThread one");
		Thread PT2=new Thread(new Producer("P_two",wh,50),"ProducerThread two");
		Thread CT2=new Thread(new Consumer("C_two",wh,30),"ConsumerrThread two");
		
//		PT1.setPriority(Thread.MIN_PRIORITY);
//		CT1.setPriority(Thread.MAX_PRIORITY);
		PT1.start();
		PT2.start();
		CT2.start();
	}
}


4.多生产者,多消费者。

public class Test_MultiP_MultiC {
	public static void main(String[] args){
		Warehouse wh=new Warehouse();
		Thread PT1=new Thread(new Producer("P_one",wh,20),"ProducerThread one");
		Thread PT2=new Thread(new Producer("P_two",wh,60),"ProducerThread two");
		Thread CT1=new Thread(new Consumer("C_one",wh,50),"ConsumerrThread one");		
		Thread CT2=new Thread(new Consumer("C_two",wh,30),"ConsumerrThread two");
//		PT1.setPriority(Thread.MIN_PRIORITY);
//		CT1.setPriority(Thread.MAX_PRIORITY);
		PT1.start();
		PT2.start();
		CT1.start();
		CT2.start();
	}
}



大概就解释到这里。考虑几点以后如果再实现的话要考虑的一些模式上的问题:

可不可以进一步分离功能,使每一个类的独立性更强,聚合性更少!

消费者与生产者只关心自己的任务,并将任务的进度向仓库类通知。

仓库类不关心消费生产的细节,只在接到通知后由两者的进度信息来做调度!

这样似乎更加符合实际……

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是Java多线程编程学习笔记之十二:生产者—消费者模型的相关内容和代码。 ## 生产者—消费者模型简介 生产者—消费者模型是一种常见的多线程并发模型,它涉及到两个角色:生产者和消费者。生产者负责生产数据,消费者负责消费数据。生产者和消费者通过一个共享的缓冲区进行通信,生产者将数据放入缓冲区,消费者从缓冲区获取数据。 在多线程编程中,生产者—消费者模型实现有多种方式,本文将介绍一种基于Java实现方式。 ## 生产者—消费者模型实现 ### 1. 定义共享缓冲区 共享缓冲区是生产者和消费者进行通信的桥梁,它需要实现以下功能: - 提供一个put方法,允许生产者将数据放入缓冲区; - 提供一个take方法,允许消费者从缓冲区获取数据; - 当缓冲区已满时,put方法应该等待; - 当缓冲区为空时,take方法应该等待。 以下是一个简单的共享缓冲区的实现: ```java public class Buffer { private int[] data; private int size; private int count; private int putIndex; private int takeIndex; public Buffer(int size) { this.data = new int[size]; this.size = size; this.count = 0; this.putIndex = 0; this.takeIndex = 0; } public synchronized void put(int value) throws InterruptedException { while (count == size) { wait(); } data[putIndex] = value; putIndex = (putIndex + 1) % size; count++; notifyAll(); } public synchronized int take() throws InterruptedException { while (count == 0) { wait(); } int value = data[takeIndex]; takeIndex = (takeIndex + 1) % size; count--; notifyAll(); return value; } } ``` 上面的Buffer类使用一个数组来表示缓冲区,size表示缓冲区的大小,count表示当前缓冲区中的元素数量,putIndex和takeIndex分别表示下一个可写和可读的位置。put和take方法都是同步方法,使用wait和notifyAll来进行线程间的等待和通知。 ### 2. 定义生产者和消费者 生产者和消费者都需要访问共享缓冲区,因此它们都需要接收一个Buffer对象作为参数。以下是生产者和消费者的简单实现: ```java public class Producer implements Runnable { private Buffer buffer; public Producer(Buffer buffer) { this.buffer = buffer; } public void run() { try { for (int i = 0; i < 10; i++) { buffer.put(i); System.out.println("Produced: " + i); Thread.sleep((int)(Math.random() * 1000)); } } catch (InterruptedException e) { e.printStackTrace(); } } } public class Consumer implements Runnable { private Buffer buffer; public Consumer(Buffer buffer) { this.buffer = buffer; } public void run() { try { for (int i = 0; i < 10; i++) { int value = buffer.take(); System.out.println("Consumed: " + value); Thread.sleep((int)(Math.random() * 1000)); } } catch (InterruptedException e) { e.printStackTrace(); } } } ``` 生产者在一个循环中不断地向缓冲区中放入数据,消费者也在一个循环中不断地从缓冲区中获取数据。注意,当缓冲区已满时,生产者会进入等待状态;当缓冲区为空时,消费者会进入等待状态。 ### 3. 测试 最后,我们可以使用下面的代码来进行测试: ```java public class Main { public static void main(String[] args) { Buffer buffer = new Buffer(5); Producer producer = new Producer(buffer); Consumer consumer = new Consumer(buffer); Thread producerThread = new Thread(producer); Thread consumerThread = new Thread(consumer); producerThread.start(); consumerThread.start(); } } ``` 在上面的代码中,我们创建了一个缓冲区对象和一个生产者对象和一个消费者对象,然后将它们分别传递给两个线程,并启动这两个线程。 运行上面的代码,我们可以看到生产者和消费者交替地进行操作,生产者不断地向缓冲区中放入数据,消费者不断地从缓冲区中获取数据。如果缓冲区已满或者为空,生产者和消费者会进入等待状态,直到缓冲区中有足够的空间或者有新的数据可用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值