本文部分摘自《Java 并发编程的艺术》

模式概述

在线程的世界里,生产者就是生产数据的线程,消费者就是消费数据的数据。生产者和消费者彼此之间不直接通信,而是通过阻塞队列进行通信,所以生产者生产完数据后不用等待消费者处理,而是直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列取,阻塞队列相当于一个缓冲区,平衡了生产者和消费者的处理能力

模式实战

假设现有需求:把各部门的邮件收集起来,统一处理归纳。可以使用生产者 - 消费者模式,启动一个线程把所有邮件抽取到队列中,消费者启动多个线程处理邮件。Java 代码如下:

public class QuickCheckEmailExtractor {

    private final ThreadPoolExecutor threadsPool;

    private final BlockingQueue<EmailDTO> emailQueue;

    private final EmailService emailService;

    public QuickCheckEmailExtractor() {
emailQueue = new LinkedBlockingQueue<>();
int corePoolSize = Runtime.getRuntime().availableProcessors() * 2;
threadsPool = new ThreadPoolExecutor(corePoolSize, corePoolSize, 101,
TimeUnit.SECONDS, new LinkedBlockingQueue<>(2000),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
emailService = new EmailService();
} public void extract() {
// 抽取所有邮件到队列里
new ExtractEmailTask().start();
// 处理队列里的邮件
check();
} private void check() {
try {
while (true) {
// 两秒内取不到就退出
EmailDTO email = emailQueue.poll(2, TimeUnit.SECONDS);
if (email == null) {
break;
}
threadsPool.submit(new CheckEmailTask());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
} protected void extractEmail() {
List<EmailDTO> allEmails = emailService.queryAllEmails();
if (allEmails == null) {
return;
}
for (EmailDTO emailDTO : allEmails) {
emailQueue.offer(emailDTO);
}
} protected void checkEmail(EmailDTO email) {
System.out.println("邮件" + email.getId() + "已处理");
} public class ExtractEmailTask extends Thread { @Override
public void run() {
extractEmail();
} } public class CheckEmailTask extends Thread { private EmailDTO email; @Override
public void run() {
checkEmail(email);
} public CheckEmailTask() {
super();
} public CheckEmailTask(EmailDTO email) {
super();
this.email = email;
}
}
}

多生产者和多消费者场景

在多核时代,多线程并发处理速度比单线程处理速度更快,所以可以使用多个线程来生产数据,多个线程来消费数据。更复杂的情况是,消费者消费完的数据,可能还要交给其他消费者继续处理,如图所示:

我们在一个长连接服务器中使用这种模式,生产者 1 负责将所有客户端发送的消息存放在阻塞队列 1 里,消费者 1 从队列里读消息,然后通过消息 ID 进行散列得到 N 个队列中的一个,然后根据编号将消息存放在不同的队列里,每个阻塞队列会分配一个线程来阻塞队列里的数据。如果消费者 2 无法消费消息,就将消息再抛回阻塞队列 1 中,交给其他消费者处理

public class MsgQueueManager {

    /**
* 消息总队列
*/
private final BlockingQueue<Message> messageQueue; /**
* 消息子队列集合
*/
private final List<BlockingQueue<Message>> subMsgQueues; private MsgQueueManager() {
messageQueue = new LinkedBlockingQueue<>();
subMsgQueues = new ArrayList<>();
} public static MsgQueueManager getInstance() {
return new MsgQueueManager();
} public void put(Message msg) {
try {
messageQueue.put(msg);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
} public Message take() {
try {
return messageQueue.take();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return null;
} /**
* 消费者线程获取子队列
*/
public BlockingQueue<Message> addSubMsgQueue() {
BlockingQueue<Message> subMsgQueue = new LinkedBlockingQueue<>();
subMsgQueues.add(subMsgQueue);
return subMsgQueue;
} /**
* 消息分发线程,负责把消息从大队列塞到小队列里
*/
class DispatchMessageTask implements Runnable { /**
* 控制消息分发开始与结束
*/
private boolean flag = true; public void setFlag(boolean flag) {
this.flag = flag;
} @Override
public void run() {
BlockingQueue<Message> subQueue;
while (flag) {
// 如果没有数据,则阻塞在这里
Message msg = take();
// 如果为空,表示没有Session连接,需要等待Session连接上来
while ((subQueue = getSubQueue()) == null) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
// 把消息放到小队列里
try {
subQueue.put(msg);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
} /**
* 均衡获取一个子队列
*/
public BlockingQueue<Message> getSubQueue() {
List<BlockingQueue<Message>> subMsgQueues = getInstance().subMsgQueues;
if (subMsgQueues.isEmpty()) {
return null;
}
int index = (int) (System.nanoTime() % subMsgQueues.size());
return subMsgQueues.get(index);
}
}
}

Java 并发编程 生产者消费者模式的更多相关文章

  1. Java多线程编程——生产者-消费者模式(1)

    生产者-消费者模式在生活中非常常见.就拿我们去餐馆吃饭为例.我们会遇到以下两种情况: 1.厨师-客人 如下图所示,生产者.消费者直接进行交互. 生产者生产出产品后,通知消费者:消费者消费后,通知生产者 ...

  2. Java设计模式之生产者消费者模式

    Java设计模式之生产者消费者模式 博客分类: 设计模式 设计模式Java多线程编程thread 转载 对于多线程程序来说,不管任何编程语言,生产者和消费者模型都是最经典的.就像学习每一门编程语言一 ...

  3. java+反射+多线程+生产者消费者模式+读取xml(SAX)入数据库mysql-【费元星Q9715234】

    java+反射+多线程+生产者消费者模式+读取xml(SAX)入数据库mysql-[费元星Q9715234] 说明如下,不懂的问题直接我[费元星Q9715234] 1.反射的意义在于不将xml tag ...

  4. java设计模式之生产者/消费者模式

    什么是生产者/消费者模式? 某个模块负责产生数据,这些数据由另一个模块来负责处理(此处的模块是广义的,可以是类.函数.线程.进程等).产生数据的模块,就形象地称为生产者:而处理数据的模块,就称为消费者 ...

  5. 【多线程】java多线程实现生产者消费者模式

    思考问题: 1.为什么用wait()+notify()实现生产者消费者模式? wait()方法可以暂停线程,并释放对象锁 notify()方法可以唤醒需要该对象锁的其他线程,并在执行完后续步骤,到了s ...

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

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

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

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

  8. Java多线程_生产者消费者模式2

    在我的上一条博客中,已经介绍到了多线程的经典案列——生产者消费者模式,但是在上篇中用的是传统的麻烦的非阻塞队列实现的.在这篇博客中我将介绍另一种方式就是:用阻塞队列完成生产者消费者模式,可以使用多种阻 ...

  9. java实现多线程生产者消费者模式

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

随机推荐

  1. Smashing Conf 2020

    Smashing Conf 2020 https://smashingconf.com/online-workshops/ events https://smashingconf.com/ny-202 ...

  2. Set-Cookie & Secure & HttpOnly & SameSite

    Set-Cookie & Secure & HttpOnly & SameSite HTTP/Headers/Set-Cookie Set-Cookie https://dev ...

  3. 如何用 js 实现一个 new 函数

    如何用 js 实现一个 new 函数 原理 new 关键字实现经过了如下过程 创建一个空对象 obj = {} 链接到原型 obj.proto = constructor.prototype 绑定 t ...

  4. macOS & PostgreSQL

    macOS & PostgreSQL macOS 上安装 PostgreSQL 后为什么会自动创建一个系统用户账号 https://get.enterprisedb.com/postgresq ...

  5. code to markdown auto converter

    code to markdown auto converter code => markdown how to import a js file to a markdown file? // a ...

  6. TweenMax & GSAP & SVG Animation

    TweenMax & GSAP & SVG Animation svg animation https://greensock.com/tweenmax GSAP https://ww ...

  7. div & drop

    div & drop uplaod file & prevent dropleave https://www.runoob.com/jsref/event-ondrop.html ht ...

  8. Flutter 将TextField平滑过渡到Text

    import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends State ...

  9. 【转】主成分分析(PCA)

    原文网址:https://blog.csdn.net/sunshine_in_moon/article/details/51513880.转载主要方便随时可以查看,如有版权要求请及时联系. 相信大家对 ...

  10. Typescript快速入门

    目录 什么是Typescript 为什么学习Typescript 快速搭建开发环境 1.安装node.js 2.使用node自带的npm安装Typescript编译器 3.配置vscode编辑环境 4 ...