在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题。该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。

为什么要使用生产者和消费者模式

在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这种生产消费能力不均衡的问题,所以便有了生产者和消费者模式。

什么是生产者消费者模式

生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。

这个阻塞队列就是用来给生产者和消费者解耦的。纵观大多数设计模式,都会找一个第三者出来进行解耦,如工厂模式的第三者是工厂类,模板模式的第三者是模板类。在学习一些设计模式的过程中,如果先找到这个模式的第三者,能帮助我们快速熟悉一个设计模式。

 
产者/消费者模型描述如下:
有一个或多个生产者生产某种类型的数据,并放置缓冲区(可以是数组也可以是队列等数据结构)中;
有一个消费者可以从缓冲区中取数据,每次取一项;
系统保证避免对缓冲区的重复操作,也就是说在任何时候只有一个主体(生产者或消费者)可以访问缓冲区。
问题要确保缓冲区不溢出,即当缓冲区满时,生成者不会继续向其中添加数据;当缓冲区空时,消费者不会从中移走数据。
 
生产者消费者模式是并发、多线程编程中经典的设计模式,生产者和消费者通过分离的执行工作解耦,简化了开发模式,生产者和消费者可以以不同的速度生产和消费数据。
 

真实世界中的生产者消费者模式

生产者和消费者模式在生活当中随处可见,它描述的是协调与协作的关系。比如一个人正在准备食物(生产者),而另一个人正在吃(消费者),他们使用一个共用的桌子用于放置盘子和取走盘子,生产者准备食物,如果桌子上已经满了就等待,消费者(那个吃的)等待如果桌子空了的话。这里桌子就是一个共享的对象。

生产者与消费者模型中,要保证以下几点:
1 同一时间内只能有一个生产者生产
2 同一时间内只能有一个消费者消费
3 生产者生产的同时消费者不能消费
4 消费者消费的同时生产者不能生产
5 共享空间空时消费者不能继续消费
6 共享空间满时生产者不能继续生产
 
 
我们看这样一个例子:
生产者:  往一个公共的盒子里面放苹果 
消费者:从公共的盒子里面取苹果
盒子:盒子的容量不能超过5
 
 
 
下面我们用两者方法分别实现这样一个场景。 
方法一:   wait()  和   notify()   通信方法实现

/** 生产者与消费者模型中,要保证以下几点:
* 1 同一时间内只能有一个生产者生产
* 2 同一时间内只能有一个消费者消费
* 3 生产者生产的同时消费者不能消费
* 4 消费者消费的同时生产者不能生产
* 5 共享空间空时消费者不能继续消费
* 6 共享空间满时生产者不能继续生产
*
* */ //容器 解耦生产者和消费者
class Box {
private int apple = 0;
private int capacity = 0;
public Box(int n) {
this.capacity = n;
}
public synchronized void increace() {
while (apple == capacity) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
apple++;
System.out.println("生产了一个");
notify();
}
public synchronized void decreace() {
while (apple == 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
apple--;
System.out.println("消费了一个");
notify();
}
} //生产者(定义十次):
class Producer implements Runnable {
private Box box; public Producer(Box box) {
this.box = box;
} @Override
public void run() {
for( int i=0;i<10;i++)
{
box.increace();
}
}
} //消费者(同样十次):
class Consumer implements Runnable {
private Box box; public Consumer(Box box) {
this.box = box;
}
@Override
public void run() {
for( int i=0;i<10;i++)
{
box.decreace();
}
}
} //测试
public class StaticTest{
public static void main(String []args)
{
Box box= new Box(5); Thread t1= new Thread(new Producer(box));
Thread t2= new Thread(new Consumer(box)); t1.start();
t2.start(); }
}
输出如下:
 

生产了一个
生产了一个
生产了一个
生产了一个
消费了一个
消费了一个
消费了一个
消费了一个
生产了一个
生产了一个
生产了一个
生产了一个
生产了一个
消费了一个
消费了一个
消费了一个
消费了一个
消费了一个
生产了一个
消费了一个

 

 
方法二:采用阻塞队列实现生产者消费者模式
 

阻塞队列实现生产者消费者模式超级简单,它提供开箱即用支持阻塞的方法put()和take(),开发者不需要写困惑的wait-nofity代码去实现通信。BlockingQueue 一个接口,Java5提供了不同的现实,如ArrayBlockingQueue和LinkedBlockingQueue,两者都是先进先出(FIFO)顺序。而ArrayLinkedQueue是自然有界的,LinkedBlockingQueue可选的边界。下面这是一个完整的生产者消费者代码例子,对比传统的wait、nofity代码,它更易于理解。

package 生产者消费者2;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue; public class PublicBoxQueue { public static void main(String []args)
{
//定义了一个大小为5的盒子
BlockingQueue publicBoxQueue= new LinkedBlockingQueue(5); Thread pro= new Thread(new Producer(publicBoxQueue));
Thread con= new Thread(new Consumer(publicBoxQueue)); pro.start();
con.start();
} } //生产者
class Producer implements Runnable {
private final BlockingQueue proQueue; public Producer(BlockingQueue proQueue)
{
this .proQueue =proQueue;
}
@Override
public void run() {
// TODO Auto-generated method stub
for (int i=0;i<10;i++)
{
try {
System. out .println("生产者生产的苹果编号为 : " +(i+1)); //放入十个苹果编号 为1到10
proQueue .put(i+1);
//Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
}
} } //消费者
class Consumer implements Runnable {
private final BlockingQueue conQueue; public Consumer(BlockingQueue conQueue)
{
this .conQueue =conQueue;
}
@Override
public void run() {
// TODO Auto-generated method stub
for (int i=0;i<10;i++)
{
try {
System. out .println("消费者消费的苹果编号为 :" +conQueue .take());
//Thread. sleep(3000); //在这里sleep是为了看的更加清楚些 } catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
}
} }
 
结果如下:

生产者生产的苹果编号为 : 1
生产者生产的苹果编号为 : 2
生产者生产的苹果编号为 : 3
消费者消费的苹果编号为 :1
消费者消费的苹果编号为 :2
消费者消费的苹果编号为 :3
生产者生产的苹果编号为 : 4
生产者生产的苹果编号为 : 5
消费者消费的苹果编号为 :4
消费者消费的苹果编号为 :5
生产者生产的苹果编号为 : 6
生产者生产的苹果编号为 : 7
生产者生产的苹果编号为 : 8
生产者生产的苹果编号为 : 9
生产者生产的苹果编号为 : 10
消费者消费的苹果编号为 :6
消费者消费的苹果编号为 :7
消费者消费的苹果编号为 :8
消费者消费的苹果编号为 :9
消费者消费的苹果编号为 :10

生产者消费者模式的好处

它的确是一种实用的设计模式,常用于编写多线程或并发代码。下面是它的一些优点:

  1. 它简化的开发,你可以独立地或并发的编写消费者和生产者,它仅仅只需知道共享对象是谁
  2. 生产者不需要知道谁是消费者或者有多少消费者,对消费者来说也是一样
  3. 生产者和消费者可以以不同的速度执行
  4. 分离的消费者和生产者在功能上能写出更简洁、可读、易维护的代码

java 多线程并发系列之 生产者消费者模式的两种实现的更多相关文章

  1. Java实现多线程生产者消费者模式的两种方法

    生产者消费者模式:生产者和消费者在同一时间段内共用同一存储空间,生产者向空间里生产数据,而消费者取走数据.生产者生产一个,消费者消费一个,不断循环. 第一种实现方法,用BlockingQueue阻塞队 ...

  2. Java多线程—阻塞队列和生产者-消费者模式

    阻塞队列支持生产者-消费者这种设计模式.该模式将“找出需要完成的工作”与“执行工作”这两个过程分离开来,并把工作项放入一个“待完成“列表中以便在随后处理,而不是找出后立即处理.生产者-消费者模式能简化 ...

  3. Java多线程-----实现生产者消费者模式的几种方式

       1 生产者消费者模式概述 生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题.生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理 ...

  4. Java 多线程学习笔记:生产者消费者问题

    前言:最近在学习Java多线程,看到ImportNew网上有网友翻译的一篇文章<阻塞队列实现生产者消费者模式>.在文中,使用的是Java的concurrent包中的阻塞队列来实现.在看完后 ...

  5. java23种设计模式专攻:生产者-消费者模式的三种实现方式

    公司的架构用到了dubbo.带我那小哥也是个半吊子,顺便就考我生产者消费者模式,顺便还考我23种java设计模式,

  6. Java笔记1 : 在生产者消费者模式中,线程通信与共享数据,死锁问题与解决办法

    本例定义了4个类,这里说一下,方便下面讲解.分别是Product(产品),Producer(生产者),Consumer(消费者), Test(测试类). 多线程之间通信与共享数据只要引用同一内存区域就 ...

  7. java多线程并发系列之闭锁(Latch)和栅栏(CyclicBarrier)

    -闭锁(Latch) 闭锁(Latch):一种同步方法,可以延迟线程的进度直到线程到达某个终点状态.通俗的讲就是,一个闭锁相当于一扇大门,在大门打开之前所有线程都被阻断,一旦大门打开所有线程都将通过, ...

  8. Java多线程并发系列之闭锁(Latch)和栅栏(CyclicBarrier)

    JAVA并发包中有三个类用于同步一批线程的行为,分别是闭锁(Latch),信号灯(Semaphore)和栅栏(CyclicBarrier).本贴主要说明闭锁(Latch)和栅栏(CyclicBarri ...

  9. 使用Lock锁生产者消费者模式

    package com.java.concurrent; import java.util.concurrent.locks.Condition; import java.util.concurren ...

随机推荐

  1. Navicat premium连接Oracle报ORA-12541错误

    1:ORA-12541 原因:Oracle TNS监听服务没开 解决:

  2. 根据判断数组不为空然后取他的值----数组不会为空---只能判断其size是否大于0

    private List<Integer> classId=new ArrayList<Integer>(); business.getClassId()!=null 以上为错 ...

  3. C语言试题(标准答案)

    C语言试题(标准答案) 一.单选题(每小题2分,共20小题40分) 1.1-1.5    B D A C B                1.6-1.10    C A D B C 1.11-1.1 ...

  4. Linux下汇编语言学习笔记70 ---

    这是17年暑假学习Linux汇编语言的笔记记录,参考书目为清华大学出版社 Jeff Duntemann著 梁晓辉译<汇编语言基于Linux环境>的书,喜欢看原版书的同学可以看<Ass ...

  5. 一个简单的js队列,逻辑很清晰

    function Queue(type) { //type 是否是一个接着一个执行 function QueueConst() {} QueueConst.execute_ing=[], QueueC ...

  6. php oop-1

    <?php //类的定义以 class 关键字开始 后面跟 类的名称 通常第一个字母大写 以 大括号开始 和结束 //类的属性和方法 前面都要加关键字 例如public class NbaPla ...

  7. Windows编程中char*转LPCWSTR解决的方法总结

    Windows编程中常常涉及到的一个问题是字符串之间的转换,开发过程总是遇到编译器提示无法格式转换的问题.于是自己总结了几种解决的方法. 1.通过T2W转换宏 char* szStr = " ...

  8. 第K顺序统计量的求解

    一个n个元素组成的集合中,第K个顺序统计量(Order Statistic)指的是该集合中第K小的元素,我们要讨论的是如何在线性时间(linear time)里找出一个数组的第K个顺序统计量. 一.问 ...

  9. Mahout--(一)数据承载

    mahout API英文解释:https://builds.apache.org/job/Mahout-Quality/javadoc/ 推荐数据的处理是大规模的,在集群环境下一次要处理的数据可能是数 ...

  10. 微软的技术态度 -- 从其对于CRT的设计考虑说起(Thought on the CRT - What Microsoft Prefers)

    很多人从C语言学习过来的人都知道,在编写程序时用到的像printf这样的函数,是作为该语言标准库函数提供的,这也是C语言标准中规定的内容.因此,操作系统必须对其保持一定程度上的透明,也就是说,作为一个 ...