一、前言

  这种模式在生活是最常见的,那么它的场景是什么样的呢? 下面是我假象的,假设有一个仓库,仓库有一个生产者和一个消费者,消费者过来消费的时候会检测仓库中是否有库存,如果没有了则等待生产,如果有就先消费直至消费完成;而生产者每天的工作就是先检测仓库是否有库存,如果没有就开始生产,满仓了就停止生产等待消费,直至工作结束。下图是根据假象画的流程图:

  那么在程序中怎么才能达到这样的效果呢?下面介绍三种方式实现。

二、使用notify() 和 wait()实现

  相信大家这两个方法都不陌生,它是Object类中的两个方法,具体请看源码中的解释。提醒一点就是使用notify()和wait()方法时必须拥有对象锁

  根据上面假象我这定义一下明确场景:仓库库存有个最大值,如果仓库库存已经达到最大值那么就停止生产,小于就需要生产; 如果库存等于0则需要等待生产停止消费。另外生产者有个生产目标,当它生产了目标数后就结束生产;消费者也是,当消费一定的数据后就结束消费,否则等待消费。

  见下面代码:

package com.yuanfy.jmm.threads;

import com.yuanfy.util.SleepUtils;

import java.util.concurrent.TimeUnit;

public class Factory {
// 当前库存大小
private int size;
// 库存容量(最大库存值)
private int capacity; public Factory(int capacity) {
this.capacity = capacity;
} public synchronized void produce(int num) {
try {
System.out.println("+++++生产者【" + Thread.currentThread().getName()
+ "】, 他的任务是生产" + num + "件产品.");
// 当生产完成就停止
while (num > 0) {
// 如果当前库存大小大于或等于库存容量值了,则停止生产等待消费。
if (size >= capacity) {
System.out.println("+++++" + Thread.currentThread().getName() +
"检测库存已满,停止生产等待消费...");
// 等待消费
wait();
System.out.println("+++++" + Thread.currentThread().getName() + "开始生产...");
}
// 否则继续生产
int inc = (num + size) > capacity ? (capacity - size) : num;
num -= inc;
size += inc;
SleepUtils.second(1);
System.out.println("+++++" + Thread.currentThread().getName() + " 生产了" + inc + "件,当前库存有" + size + "件.");
// 生产后唤醒消费者
notify();
}
System.out.println("+++++生产者【" + Thread.currentThread().getName()
+ "】 生产结束.");
} catch (InterruptedException e) {
e.printStackTrace();
}
} public synchronized void consume(int num) {
try {
System.out.println("-----消费者【" + Thread.currentThread().getName()
+ "】, 他需要消费" + num + "件产品.");
// 当消费完成则停止
while (num > 0) {
// 如果当前库存大小小于等于0,则停止消费等待生产。
if (size <= 0) {
System.out.println("-----" + Thread.currentThread().getName() + " 检测库存已空,停止消费等待生产...");
// 等待生产
wait();
System.out.println("-----" + Thread.currentThread().getName() + " 开始消费...");
}
// 否则继续消费
int dec = (size - num) > 0 ? num : size;
num -= dec;
size -= dec;
SleepUtils.second(1);
System.out.println("-----" + Thread.currentThread().getName() + " 消费了" + dec + "件,当前有" + size + "件.");
// 消费后唤醒生产者继续生产
notify();
}
System.out.println("-----消费者【" + Thread.currentThread().getName()
+ "】 消费结束.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

  上面是工厂(仓库)类,主要包含两个任务一个是生产一个是消费,接下来创建两个线程去调用它,如下:

package com.yuanfy.jmm.threads;

/**
* 生产线程
*/
class Produce {
private Factory factory; public Produce(Factory factory) {
this.factory = factory;
} public void produce(String name, final int num) {
new Thread(new Runnable() {
@Override
public void run() {
factory.produce(num);
}
}, name).start();
}
}
/**
* 消费线程
*/
class Consume {
private Factory factory; public Consume(Factory factory) {
this.factory = factory;
} public void consume(String name, final int num) {
new Thread(new Runnable() {
@Override
public void run() {
factory.consume(num);
}
}, name).start();
}
} public class ProduceConsumeDemo { public static void main(String[] args) {
Factory f = new Factory(500); Consume consume = new Consume(f);
consume.consume("消费线程",600); Produce produce = new Produce(f);
produce.produce("生产线程",800);
}
}

  注意上方,消费线程和生产线程都是拥有同一个工厂对象,然后进行生产和消费模式。那么我们直接运行,结果如下:

  

三、使用锁中的Condition对象进行控制

  这种方式估计用的比较少,因为使用Condition必须先使用锁Lock。这里我只介绍怎么用Condition对象进行控制实现生产者与消费者模式的实现。

  其实它跟上面那种方法有点类似,Condition对象中await()方法表示等待,signal()方法表示唤醒(看了AQS源码的应该都知道有这个对象且了解过这两个方法)。下面看下具体怎么实现:

public class Factory {
// 当前大小
private int size; // 总容量
private int capacity; private Lock lock; // 已满的条件
private Condition fullCondition; // 已空的条件
private Condition emptyCondition; public Factory(int capacity) {
this.capacity = capacity;
lock = new ReentrantLock();
fullCondition = lock.newCondition();
emptyCondition = lock.newCondition();
} public void produce(int no) {
lock.lock();
try {
while (no > 0) {
while (size >= capacity) {
System.out.println(Thread.currentThread().getName() + " 报告仓库已满,等待快递员取件...");
fullCondition.await();
System.out.println(Thread.currentThread().getName() + " 报告开始进货...");
}
int inc = (no + size) > capacity ? (capacity - size) : no;
no -= inc;
size += inc;
System.out.println(Thread.currentThread().getName() +
" 报告进货了: " + inc + "件, 当前库存数: " + size);
emptyCondition.signal();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
} public void consume(int no) {
lock.lock();
try {
while (no > 0) {
while (size <= 0) {
System.out.println(Thread.currentThread().getName() + " 报告仓库已空,等待仓库管理员进货");
emptyCondition.await();
System.out.println(Thread.currentThread().getName() + " 报告开始取件...");
}
int dec = (size - no) > 0 ? no : size;
no -= dec;
size -= dec;
System.out.println(Thread.currentThread().getName() +
" 报告取件: " + dec + ", 当前库存数: " + size);
fullCondition.signal();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}

  看了上面工厂类的代码后是不是跟使用Object中wait()和notify()方法类似呢。 主要区别就是拥有对象的方式不一样,这里使用的lock进行且需要手动释放,而第一种是需要Synchronized进行控制。

四、使用阻塞队列进行实现

  这个就很简单了,它已经封装好等待和唤醒的操作,所以不进行案例分享了。其中涉及到两个重要方法put() 和 take

  

Java并发编程(4)--生产者与消费者模式介绍的更多相关文章

  1. JAVA并发实现五(生产者和消费者模式wait和notify方式实现)

    package com.subject01; import java.util.PriorityQueue; /** * 通过wait和notify 实现 * 生产者-消费者模型:当队列满时,生产者需 ...

  2. JAVA并发实现五(生产者和消费者模式Condition方式实现)

    package com.subject01; import java.util.PriorityQueue; import java.util.concurrent.locks.Condition; ...

  3. Java多线程设计模式(2)生产者与消费者模式

    1 Producer-Consumer Pattern Producer-Consumer Pattern主要就是在生产者与消费者之间建立一个“桥梁参与者”,用来解决生产者线程与消费者线程之间速度的不 ...

  4. JUC 并发编程--02,生产者和消费者 synchronized的写法 , juc的写法. Condition的用法

    synchronized的写法 class PCdemo{ public static void main(String[] args) { //多个线程操作同一资源 Data data = new ...

  5. Java并发编程:Thread类的使用介绍

    在学习Thread类之前,先介绍与线程相关知识:线程的几种状态.上下文切换,然后接着介绍Thread类中的方法的具体使用. 以下是本文的目录大纲: 一.线程的状态 二.上下文切换 三.Thread类中 ...

  6. 【Java并发编程】并发编程大合集-值得收藏

    http://blog.csdn.net/ns_code/article/details/17539599这个博主的关于java并发编程系列很不错,值得收藏. 为了方便各位网友学习以及方便自己复习之用 ...

  7. 【Java并发编程】并发编程大合集

    转载自:http://blog.csdn.net/ns_code/article/details/17539599 为了方便各位网友学习以及方便自己复习之用,将Java并发编程系列内容系列内容按照由浅 ...

  8. 《Java并发编程实战》学习笔记 线程安全、共享对象和组合对象

    Java Concurrency in Practice,一本完美的Java并发参考手册. 查看豆瓣读书 推荐:InfoQ迷你书<Java并发编程的艺术> 第一章 介绍 线程的优势:充分利 ...

  9. Java 并发编程 生产者消费者模式

    本文部分摘自<Java 并发编程的艺术> 模式概述 在线程的世界里,生产者就是生产数据的线程,消费者就是消费数据的数据.生产者和消费者彼此之间不直接通信,而是通过阻塞队列进行通信,所以生产 ...

随机推荐

  1. poj 1966(顶点连通度)

    题意:给出一个n个节点和m条边的图,求该图的顶点连通度. 分析: 顶点连通度的求解可以转换为网络最大流问题. (1)原图G中的每个顶点v变成网络中的两个顶点v‘和v’‘,顶点v’至v''有一个条弧(有 ...

  2. 反爬虫之JS反编译:PyExecJS

    PyExecJS是python运行JavaScript代码的包 安装: pip install  PyExecJS (或者easy_install PyExecJS) 依赖:  需要有执行JS的程序, ...

  3. 探秘SpringAop(一)_介绍以及使用详解

    常用的编程范式 AOP 是什么 是一种编程方式,不是编程语言 解决特定问题,不能解决所有的问题 OOP的补充,不是代替 AOP 初衷 DRY: Don't repeat yourself(代码重复) ...

  4. C# 单例模式的多种简单实现

    什么是单例模式? 这里我就不做过多的解释了, 毕竟关于Singleton的资料实在是太多太多了.点击这里 1.简单的思路就是, 创建对象单例的动作转移到另外的行为上面, 利用一个行为去创建对象自身, ...

  5. python爬虫headers设置后无效解决方案

    此次遇到的是一个函数使用不熟练造成的问题,但有了分析工具后可以很快定位到问题(此处推荐一个非常棒的抓包工具fiddler) 正文如下: 在爬取某个app数据时(app上的数据都是由http请求的),用 ...

  6. 【转】VS2012简单的使用感受+插件推荐

    转自:http://blog.sina.com.cn/s/blog_58c506600101b34t.html ~PS:后面有博主自己补充的VS2013的哦~ 1.Visual Studio Achi ...

  7. 【刷题】BZOJ 3994 [SDOI2015]约数个数和

    Description 设d(x)为x的约数个数,给定N.M,求 Input 输入文件包含多组测试数据. 第一行,一个整数T,表示测试数据的组数. 接下来的T行,每行两个整数N.M. Output T ...

  8. Mininet 系列实验(三)

    实验内容 基础 Mininet 可视化界面进行自定义拓扑及拓扑设备自定义设置,实现自定义脚本应用. 参考 Mininet可视化应用 实验环境 虚拟机: Oracle VM VirtualBox Ubu ...

  9. Luogu 1020 导弹拦截(动态规划,最长不下降子序列,二分,STL运用,贪心,单调队列)

    Luogu 1020 导弹拦截(动态规划,最长不下降子序列,二分,STL运用,贪心,单调队列) Description 某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统.但是这种导弹拦截系统有一个缺 ...

  10. 特征降维之PCA

    目录 PCA思想 问题形式化表述 PCA之协方差矩阵 协方差定义 矩阵-特征值 PCA运算步骤 PCA理论解释 最大方差理论 性质 参数k的选取 数据重建 主观理解 应用 代码示例 PCA思想 PCA ...