生产者 - 消费者模式在编程领域的应用非常广泛,前面我们曾经提到,Java 线程池本质上就是用生产者 - 消费者模式实现的,所以每当使用线程池的时候,其实就是在应用生产者 - 消费者模式。

当然,除了在线程池中的应用,为了提升性能,并发编程领域很多地方也都用到了生产者 - 消费者模式,例如 Log4j2 中异步 Appender 内部也用到了生产者 - 消费者模式。所以我们就来深入地聊聊生产者 - 消费者模式,看看它具体有哪些优点,以及如何提升系统的性能。

生产者 - 消费者模式的优点

生产者 - 消费者模式的核心是一个任务队列 ,生产者线程生产任务,并将任务添加到任务队列中,而消费者线程从任务队列中获取任务并执行。下面是生产者 - 消费者模式的一个示意图,你可以结合它来理解。

生产者 - 消费者模式示意图####

从架构设计的角度来看,生产者 - 消费者模式有一个很重要的优点,就是解耦 。解耦对于大型系统的设计非常重要,而解耦的一个关键就是组件之间的依赖关系和通信方式必须受限。在生产者 - 消费者模式中,生产者和消费者没有任何依赖关系,它们彼此之间的通信只能通过任务队列,所以生产者 - 消费者模式是一个不错的解耦方案

除了架构设计上的优点之外,生产者 - 消费者模式还有一个重要的优点就是支持异步,并且能够平衡生产者和消费者的速度差异 。在生产者 - 消费者模式中,生产者线程只需要将任务添加到任务队列而无需等待任务被消费者线程执行完,也就是说任务的生产和消费是异步的,这是与传统的方法之间调用的本质区别。

异步化处理最简单的方式就是创建一个新的线程去处理,那中间增加一个任务队列 ”究竟有什么用呢?主要还是用于平衡生产者和消费者的速度差异 。我们假设生产者的速率很慢,而消费者的速率很高,比如是 1:3,如果生产者有 3 个线程,采用创建新的线程的方式,那么会创建 3 个子线程,而采用生产者 - 消费者模式,消费线程只需要 1 个就可以了。

Java 语言里,Java 线程和操作系统线程是一一对应的,线程创建得太多,会增加上下文切换的成本,所以 Java 线程不是越多越好,适量即可。

支持批量执行以提升性能

两阶段终止模式:优雅地终止线程 中,我们提到一个监控系统动态采集的案例,其实最终回传的监控数据还是要存入数据库的(如下图)。但被监控系统往往有很多,如果每一条回传数据都直接 INSERT 到数据库,那DB压力就非常大了。很显然,更好的方案是批量执行 SQL,那如何实现呢?这就要用到生产者 - 消费者模式了。

动态采集功能示意图 ####

利用生产者 - 消费者模式实现批量执行 SQL 非常简单:将原来直接 INSERT 数据到数据库的线程作为生产者线程,生产者线程只需将数据添加到任务队列,然后消费者线程负责将任务从任务队列中批量取出并批量执行。

在下面的示例代码中,我们创建了 5 个消费者线程负责批量执行 SQL,这 5 个消费者线程以while(true){} 循环方式批量地获取任务并批量地执行。需要注意的是,从任务队列中获取批量任务的方法 pollTasks() 中,首先是以阻塞方式获取任务队列中的一条任务,而后则是以非阻塞的方式获取任务;之所以首先采用阻塞方式,是因为如果任务队列中没有任务,这样的方式能够避免无谓的循环。

// 任务队列BlockingQueue bq=new LinkedBlockingQueue<>(2000);// 启动 5 个消费者线程// 执行批量任务 void start() { ExecutorService es=xecutors .newFixedThreadPool(5); for (int i=0; i<5; i++) { es.execute(()->{ try { while (true) { // 获取批量任务 List ts=pollTasks(); // 执行批量任务 execTasks(ts); } } catch (Exception e) { e.printStackTrace(); } }); }}// 从任务队列中获取批量任务List pollTasks()  throws InterruptedException{ List ts=new LinkedList<>(); // 阻塞式获取一条任务 Task t = bq.take(); while (t != null) { ts.add(t); // 非阻塞式获取一条任务 t = bq.poll(); } return ts;}// 批量执行任务execTasks(List ts) { // 省略具体代码无数}

支持分阶段提交以提升性能

利用生产者 - 消费者模式还可以轻松地支持一种分阶段提交的应用场景。我们知道写文件如果同步刷盘性能会很慢,所以对于不是很重要的数据,我们往往采用异步刷盘的方式。我曾经参与过一个项目,其中的日志组件是自己实现的,采用的就是异步刷盘方式,刷盘的时机是:

  1. ERROR 级别的日志需要立即刷盘;
  2. 数据积累到 500 条需要立即刷盘;
  3. 存在未刷盘数据,且 5 秒钟内未曾刷盘,需要立即刷盘。

这个日志组件的异步刷盘操作本质上其实就是一种分阶段提交 。下面我们具体看看用生产者 - 消费者模式如何实现。在下面的示例代码中,可以通过调用info()和error() 方法写入日志,这两个方法都是创建了一个日志任务 LogMsg,并添加到阻塞队列中,他们两个方法的线程是生产者;而真正将日志写入文件的是消费者线程,在 Logger 这个类中,我们只创建了 1 个消费者线程,在这个消费者线程中,会根据刷盘规则执行刷盘操作。

class Logger { // 任务队列  final BlockingQueue bq = new BlockingQueue<>(); //flush 批量  static final int batchSize=500; // 只需要一个线程写日志 ExecutorService es =  Executors.newFixedThreadPool(1); // 启动写日志线程 void start(){ File file=File.createTempFile( "foo

delphi生产者消费者模式代码_并发设计模式:生产者-消费者模式,并发提高效率...相关推荐

  1. netflix 模式创新_创新设计模式:工厂模式

    netflix 模式创新 以前,我们对创建模式进行了介绍,并使用抽象工厂模式来创建对象族. 下一个模式是Factory模式 . 当涉及到Java时,工厂模式是最常用的模式之一. 那么工厂模式到底是什么 ...

  2. 策略模式示例代码_策略设计模式示例

    策略模式示例代码 本文是我们名为" Java设计模式 "的学院课程的一部分. 在本课程中,您将深入研究大量的设计模式,并了解如何在Java中实现和利用它们. 您将了解模式如此重要的 ...

  3. activexobject对象不能创建_【设计模式】建造者模式:你创建对象的方式有它丝滑吗?...

    目录 什么是建造者模式 为什么要使用建造者模式 构造函数创建对象 set方式构建对象 java实现建造者模式 第一种实现方式 第二种方式 建造者模式与构造函数的对比 建造者模式与工厂模式的对比 总结 ...

  4. mave工程中的一个类调用另一个聚合工程的一个类_谈谈设计模式:建造者模式在jdk中的体现,它和工厂模式区别?...

    背景 建造模式(Builder模式) 假如有一个需求:盖房子,盖房子过程是一致的:打桩.砌墙.封顶.但是房子是各式各样的,最后盖出来的房子可能是高楼或别墅. 根据直接的思路,不用设计模式思想,我们也许 ...

  5. netflix 模式创新_创新设计模式:单例模式

    netflix 模式创新 单例设计模式是一种软件设计模式,用于将类的实例化限制为一个对象. 与其他创建设计模式(例如抽象工厂 , 工厂和构建器模式)相比,单例将创建一个对象,但也将负责,因此该对象只有 ...

  6. 设计模式 原型模式_创新设计模式:原型模式

    设计模式 原型模式 原型模式用于创建对象的副本. 这种模式非常有用,特别是当从头开始创建对象的成本很高时. 与builder , factory和abstract factory模式相比,它不会从头开 ...

  7. php工厂模式和单例模式,php 设计模式之工厂模式、单例模式、注册树模式

    php 设计模式之工厂模式.单例模式.注册树模式 在软件工程中,创建型设计模式承担着对象创建的职责,尝试创建适合程序上下文的对象,对象创建设计模式的产生是由于软件工程设计的问题,具体说是向设计中增加复 ...

  8. 策略模式和工厂模式的区别_java设计模式之状态模式,策略模式孪生兄弟

    状态模式 状态模式(State Pattern)中,类的行为是基于它的状态改变的,状态之间的切换,在状态A执行完毕后自己控制状态指向状态B,状态模式是不停的切换状态执行.这种类型的设计模式属于行为型模 ...

  9. java 工厂模式详解_Java设计模式之工厂模式详解

    简单工厂其实并不是设计模式,只是一种编程习惯. 首先我们创建父类Cup,所有杯子类的父类.再创建它的子类BigCup和SmallCup类. public abstract class Cup { pu ...

最新文章

  1. css 实现打分效果
  2. LifseaOS 悄然来袭,一款为云原生而生的 OS
  3. 三次握手和四次挥手详细介绍
  4. 雷蛇鼠标宏文件_《硬事要说34》稳接曼巴的旗?雷蛇巴塞利斯蛇[终极版]解读...
  5. 2020年10月份Github上热门的开源项目
  6. 苹果将允许iPhone直接使用NFC接受信用卡付款
  7. Linux学习总结(16)——CentOS 下 Nginx + Tomcat 配置负载均衡
  8. 下载了linux版redis怎么用,Linux下redis的安装与使用图文教程
  9. EF架构~为ObjectContext类型加个Find方法
  10. java设置cookie_java之Cookie详解
  11. iOS 音乐播放器之锁屏歌词+歌词解析+锁屏效果
  12. 口算加密php怎么使用,从数盲到口算 ——带你玩转RSA加密算法(一)
  13. 鸿蒙陶瓷制造中心,法库县陶瓷创意中心
  14. google书签找回
  15. Kafka offset 偏移量详解
  16. python matplotlib 设置画布大小
  17. RN-原生混合开发之热更新
  18. soff被判3年,软件业是福是祸?
  19. 一篇差点让老婆离开我的文章
  20. SGU 187 Twist and whirl - want to cheat(splay)

热门文章

  1. Silverlight 属性样式、控件模板、视觉状态
  2. Bootstrap的学习分享
  3. c++ 字典_再来瞄一眼字典与集合?
  4. 软件测试工程师-Python语言
  5. iosid不足以修改问题_寻找合作伙伴的技巧足以与您合作
  6. app个人健康管理系统开源_开源会促进心理健康吗?
  7. c语言程序设计 猜数字,C语言程序设计(猜数字游戏)报告.doc
  8. c语言用字符串统计一个整数中数字的个数_全国计算机等级考试二级C语言
  9. android 通过图片url获取宽高_通过 URL 获取图片宽高优化
  10. mysql 导入dmp_mysql导入导出sql文件