一、前言

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

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

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

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

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

  见下面代码:

  1. package com.yuanfy.jmm.threads;
  2.  
  3. import com.yuanfy.util.SleepUtils;
  4.  
  5. import java.util.concurrent.TimeUnit;
  6.  
  7. public class Factory {
  8. // 当前库存大小
  9. private int size;
  10. // 库存容量(最大库存值)
  11. private int capacity;
  12.  
  13. public Factory(int capacity) {
  14. this.capacity = capacity;
  15. }
  16.  
  17. public synchronized void produce(int num) {
  18. try {
  19. System.out.println("+++++生产者【" + Thread.currentThread().getName()
  20. + "】, 他的任务是生产" + num + "件产品.");
  21. // 当生产完成就停止
  22. while (num > 0) {
  23. // 如果当前库存大小大于或等于库存容量值了,则停止生产等待消费。
  24. if (size >= capacity) {
  25. System.out.println("+++++" + Thread.currentThread().getName() +
  26. "检测库存已满,停止生产等待消费...");
  27. // 等待消费
  28. wait();
  29. System.out.println("+++++" + Thread.currentThread().getName() + "开始生产...");
  30. }
  31. // 否则继续生产
  32. int inc = (num + size) > capacity ? (capacity - size) : num;
  33. num -= inc;
  34. size += inc;
  35. SleepUtils.second(1);
  36. System.out.println("+++++" + Thread.currentThread().getName() + " 生产了" + inc + "件,当前库存有" + size + "件.");
  37. // 生产后唤醒消费者
  38. notify();
  39. }
  40. System.out.println("+++++生产者【" + Thread.currentThread().getName()
  41. + "】 生产结束.");
  42. } catch (InterruptedException e) {
  43. e.printStackTrace();
  44. }
  45. }
  46.  
  47. public synchronized void consume(int num) {
  48. try {
  49. System.out.println("-----消费者【" + Thread.currentThread().getName()
  50. + "】, 他需要消费" + num + "件产品.");
  51. // 当消费完成则停止
  52. while (num > 0) {
  53. // 如果当前库存大小小于等于0,则停止消费等待生产。
  54. if (size <= 0) {
  55. System.out.println("-----" + Thread.currentThread().getName() + " 检测库存已空,停止消费等待生产...");
  56. // 等待生产
  57. wait();
  58. System.out.println("-----" + Thread.currentThread().getName() + " 开始消费...");
  59. }
  60. // 否则继续消费
  61. int dec = (size - num) > 0 ? num : size;
  62. num -= dec;
  63. size -= dec;
  64. SleepUtils.second(1);
  65. System.out.println("-----" + Thread.currentThread().getName() + " 消费了" + dec + "件,当前有" + size + "件.");
  66. // 消费后唤醒生产者继续生产
  67. notify();
  68. }
  69. System.out.println("-----消费者【" + Thread.currentThread().getName()
  70. + "】 消费结束.");
  71. } catch (InterruptedException e) {
  72. e.printStackTrace();
  73. }
  74. }
  75. }

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

  1. package com.yuanfy.jmm.threads;
  2.  
  3. /**
  4. * 生产线程
  5. */
  6. class Produce {
  7. private Factory factory;
  8.  
  9. public Produce(Factory factory) {
  10. this.factory = factory;
  11. }
  12.  
  13. public void produce(String name, final int num) {
  14. new Thread(new Runnable() {
  15. @Override
  16. public void run() {
  17. factory.produce(num);
  18. }
  19. }, name).start();
  20. }
  21. }
  22. /**
  23. * 消费线程
  24. */
  25. class Consume {
  26. private Factory factory;
  27.  
  28. public Consume(Factory factory) {
  29. this.factory = factory;
  30. }
  31.  
  32. public void consume(String name, final int num) {
  33. new Thread(new Runnable() {
  34. @Override
  35. public void run() {
  36. factory.consume(num);
  37. }
  38. }, name).start();
  39. }
  40. }
  41.  
  42. public class ProduceConsumeDemo {
  43.  
  44. public static void main(String[] args) {
  45. Factory f = new Factory(500);
  46.  
  47. Consume consume = new Consume(f);
  48. consume.consume("消费线程",600);
  49.  
  50. Produce produce = new Produce(f);
  51. produce.produce("生产线程",800);
  52. }
  53. }

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

  

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

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

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

  1. public class Factory {
  2. // 当前大小
  3. private int size;
  4.  
  5. // 总容量
  6. private int capacity;
  7.  
  8. private Lock lock;
  9.  
  10. // 已满的条件
  11. private Condition fullCondition;
  12.  
  13. // 已空的条件
  14. private Condition emptyCondition;
  15.  
  16. public Factory(int capacity) {
  17. this.capacity = capacity;
  18. lock = new ReentrantLock();
  19. fullCondition = lock.newCondition();
  20. emptyCondition = lock.newCondition();
  21. }
  22.  
  23. public void produce(int no) {
  24. lock.lock();
  25. try {
  26. while (no > 0) {
  27. while (size >= capacity) {
  28. System.out.println(Thread.currentThread().getName() + " 报告仓库已满,等待快递员取件...");
  29. fullCondition.await();
  30. System.out.println(Thread.currentThread().getName() + " 报告开始进货...");
  31. }
  32. int inc = (no + size) > capacity ? (capacity - size) : no;
  33. no -= inc;
  34. size += inc;
  35. System.out.println(Thread.currentThread().getName() +
  36. " 报告进货了: " + inc + "件, 当前库存数: " + size);
  37. emptyCondition.signal();
  38. }
  39. } catch (InterruptedException e) {
  40. e.printStackTrace();
  41. } finally {
  42. lock.unlock();
  43. }
  44. }
  45.  
  46. public void consume(int no) {
  47. lock.lock();
  48. try {
  49. while (no > 0) {
  50. while (size <= 0) {
  51. System.out.println(Thread.currentThread().getName() + " 报告仓库已空,等待仓库管理员进货");
  52. emptyCondition.await();
  53. System.out.println(Thread.currentThread().getName() + " 报告开始取件...");
  54. }
  55. int dec = (size - no) > 0 ? no : size;
  56. no -= dec;
  57. size -= dec;
  58. System.out.println(Thread.currentThread().getName() +
  59. " 报告取件: " + dec + ", 当前库存数: " + size);
  60. fullCondition.signal();
  61. }
  62. } catch (InterruptedException e) {
  63. e.printStackTrace();
  64. } finally {
  65. lock.unlock();
  66. }
  67. }
  68. }

  看了上面工厂类的代码后是不是跟使用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. SpringBoot(九)_springboot集成 MyBatis

    MyBatis 是一款标准的 ORM 框架,被广泛的应用于各企业开发中.具体细节这里就不在叙述,大家自行查找资料进行学习下. 加载依赖 <dependency> <groupId&g ...

  2. jquery datatables 添加跳转到指定页功能

    项目中使用了jquery datatables 作为我们的数据表格组件,但是分页上没有跳转到指定页,需要自己重新写.解决方法如下: 在设置dataTables的默认属性里设置它的drawCallbac ...

  3. Shopping Bands Rank & SBR

    Shopping Bands Rank SBR https://www.guiderank.org/index.html Nike Air Zoom Pegasus 34 http://www.shi ...

  4. Linux/Unix系统编程手册 第三章:系统编程概念

    本章介绍系统编程的基础概念和一些后续章节用到的函数及头文件,并说明了可移植性问题. 系统调用是受控的内核入口,通过系统调用,进程可以请求内核以自己的名义去执行某些动作,比如创建子进程,执行I/O操作, ...

  5. 当给属性添加final 时候 则无法进行第二次值的修改

  6. Window系统 安装TFLearn

    1. 确保成功安装了tensorflow 2. 查看当前tensorflow下的库文件,判断是否已经安装了h5py,scipy:conda list 3. 若没有安装,安装h5py,scipy.我的电 ...

  7. BZOJ5102 POI2018Prawnicy(堆)

    考虑固定右端点,使左端点最小.那么按右端点排序后查询前缀这些区间的左端点第k小即可.然而写了一个treap一个线段树都T飞了,感觉惨爆.事实上可以用堆求第k小,维护一个大根堆保证堆中元素不超过k个即可 ...

  8. BZOJ3173 TJOI2013最长上升子序列(splay)

    容易发现如果求出最后的序列,只要算一下LIS就好了.序列用平衡树随便搞一下,这里种一棵splay. #include<iostream> #include<cstdio> #i ...

  9. 【Learning】辛普森积分

    辛普森积分 这种积分法很暴力:只要求你实现出函数求值\(f(x)\). 使用辛普森积分,我们可以求出函数一段区间\([l,r]\)的近似积分.记\(mid=\frac{l+r}2\),有: \[ \i ...

  10. 【Cf Edu #47 F】Dominant Indices(长链剖分)

    要求每个点子树中节点最多的层数,一个通常的思路是树上启发式合并,对于每一个点,保留它的重儿子的贡献,暴力扫轻儿子将他们的贡献合并到重儿子里来. 参考重链剖分,由于一个点向上最多只有$log$条轻边,故 ...