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

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

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

什么是生产者消费者模式

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

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

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

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

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

生产者与消费者模型中,要保证以下几点:
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. BAT经典面试题,深入理解Java内存模型JMM

    Java 内存模型 Java 内存模型(JMM)是一种抽象的概念,并不真实存在,它描述了一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段.静态字段和构成数组对象的元素)的访问方式.试图屏 ...

  2. Ubuntu查看系统版本的方法

    1. less /etc/issue 2. less /proc/version 3. uname -a 4. lsb_release -a

  3. Ubuntu 16.04安装VLC播放器,替代系统默认播放器

    VLC播放器应该说是开源项目中最好的视频播放器,但功能不止于视频播放,还有视频直播等等.可以通过安装字幕插件搜索字母等. 安装步骤: 1.安装: sudo add-apt-repository ppa ...

  4. ZooKeeper实现配置中心的实例(原生API实现)(转)

    说明:要实现配置中心的例子,可以选择的SDK有很多,原生自带的SDK也是不错的选择.比如使用I0Itec,Spring Boot集成等. 大型应用通常会按业务拆分成一个个业务子系统,这些大大小小的子应 ...

  5. ojdbc.jar

    Oracle的jdbc驱动是ojdbc.jar 文件,那么mysql的jdbc驱动是什么呢? 匿名 | 浏览 689 次 发布于2015-06-07 02:06   最佳答案   MySQL的JDBC ...

  6. 墨卡托坐标与LBS应用

    今天了解到这边的LBS应用,一般用的是墨卡托坐标. 也就是商品库的商品入库的时候,会根据输入,使用百度地图提供的一个API,来转换成一个墨卡托坐标. 然后用户流量过来的时候,会带来历史坐标,和当前坐标 ...

  7. javascript 事件对象(event 对象)

    原文: http://www.cnblogs.com/songyaqi/p/5204143.html <html> <head> <title> Track Mou ...

  8. 第3章 ES文档和故障处理

    第3章 ES文档和故障处理 一.ES网络配置表 ES网络配置表是ES的硬件和软件组成的列表.ES网络配置常包括以下项目: 分级 项目 杂项信息 系统名.系统厂商/型号.CPU速率.RAM.存储器.系统 ...

  9. Mysql net start mysql启动,提示发生系统错误 5 拒绝訪问 解决之道

    当前用户的操作权限太低了,出了问题 出错问题截屏例如以下: watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/4 ...

  10. 【RefactoringCode】The description of the refactoring book

    Last night the book named [Data Structure with Java Hubbed] was closed. When talked about the advant ...