生产者/消费者模式

 

实际上,很多后台服务程序并发控制的基本原理都可以归纳为生产者/消费者模式。

生产者消费问题是研究多线程程序时绕不开的经典问题之一,它描述是有一块缓冲区作为仓库,消费者则可以从仓库中取走产品。解决生产者/消费者问题的方法可以分为两类:

  1. 采用某种机制保护生产者和消费者之间的同步;

  2. 生产者和消费者之间建立一个管道。

第一种方式有较高的效率,并且易于实现,代码的可控制性较好,属于常用的模式。

第二种管道缓冲不易控制,被传输数据对象不易于封装,实用性不强。

同步问题的核心在于:如何保证同一资源被多个线程并发访问时的完整性。

准确说应该是"生产者-消费者-仓库"模型,离开了仓库,生产者消费者模型就显得没有说服力了。

对于此模型,应该明确一下几点:

  1. 生产者仅仅在仓库未满时候生产,仓满则停止生产。

  2. 消费者仅仅在仓库有产品的时候才能消费,仓空则等待。

  3. 当消费者发现仓库没产品可消费时候会通知生产者生产。

  4. 生产者在生产出可消费产品时候,应该通知等待的消费者取消费。

 

 

光看文字可能不太好理解,下面是案例:

1、生产者模块

 

package com.edu.qwd;


import javax.swing.plaf.synth.SynthSpinnerUI;

//生产者线程要执行的任务
public class Producer implements Runnable {
 private Clerk cl;
 public Producer(Clerk cl){
  this.cl=cl;
 }
 
 public void run(){
  System.out.println("生产者开始生产产品!");
  while(true){
   try {
    Thread.sleep((int)(Math.random()*10)*100);
   } catch (InterruptedException e) {
    // TODO 自动生成的catch块
    e.printStackTrace();
   }
   cl.addproduct();//生产产品
  }
 }

}

 

2、缓冲电力模块

package com.edu.qwd;

public class Clerk {
 private int product=0;//产品默认为0;
 //生产者生成出来的产品交给店员
 public synchronized void addproduct(){
  if(this.product>=20){
   try {
    wait();//产品已满,请稍等再生产
   } catch (InterruptedException e) {
    //自动生成的catch块
    e.printStackTrace();
   }
  }else{
   product++;
   System.out.println("生产者生产产品"+product+"个产品。");
   notifyAll();//通知等待区的消费者今天取产品了
  }
 }
 
 //消费者从店员处取产品
 public synchronized void getProduct(){
  if(this.product<=0){
   try {
    wait();//产品没有货了,请稍等再取
   } catch (InterruptedException e) {
    // TODO 自动生成catch块
    e.printStackTrace();
   }
  }else{
   System.out.println("消费者取走了第"+product+"个产品");
   product--;
   notify();//通知等待区的生产者可以生产产品
  }
 }
}

 

3、消费者模块

package com.edu.qwd;
//消费者线程要执行的任务
public class Consumer implements Runnable{
 private Clerk cl;
 public Consumer(Clerk cl){
  this.cl=cl;
 }
 public void run(){
  System.out.println("消费者开始取走产品!");
  while(true){
   try {
    Thread.sleep((int)(Math.random()*10)*100);
   } catch (InterruptedException e) {
    // TODO 自动生成的catch块
    e.printStackTrace();
   }
   cl.getProduct();//取走产品
  }
 }

}

4、最后Main方法输出就可以了

package com.edu.qwd;

public class Main {
public static void main(String[] args) {
 //自动生成的方法存根
 Clerk cl=new Clerk();
 Thread prt=new Thread(new Producer(cl));//生产者线程
 Thread cot=new Thread(new Consumer(cl));//消费者线程
 prt.start();
 cot.start();
 }
}

 

使用了生产者/消费者模式之后,生产者和消费者可以是两个独立的并发主体(常见并发类型有进程和线程两种)。生产者把制造出来的数据往缓冲区一丢,就可以去生产下一个数据。基本上不用依赖消费者的处理数据。