Java 多线程基础(十二)生产者与消费者

一、生产者与消费者模型

生产者与消费者问题是个非常典型的多线程问题,涉及到的对象包括“生产者”、“消费者”、“仓库”和“产品”。他们之间的关系如下:

①、生产者仅仅在仓储未满时候生产,仓满则停止生产。
②、消费者仅仅在仓储有产品时候才能消费,仓空则等待。
③、当消费者发现仓储没产品可消费时候会通知生产者生产。
④、生产者在生产出可消费产品时候,应该通知等待的消费者去消费。

生产者消费者模型具体来讲,就是在一个系统中,存在生产者和消费者两种角色,他们通过内存缓冲区进行通信,生产者生产消费者需要的资料,消费者把资料做成产品。生产消费者模式如下图。

在日益发展的服务类型中,譬如注册用户这种服务,它可能解耦成好几种独立的服务(账号验证,邮箱验证码,手机短信码等)。它们作为消费者,等待用户输入数据,在前台数据提交之后会经过分解并发送到各个服务所在的url,分发的那个角色就相当于生产者。消费者在获取数据时候有可能一次不能处理完,那么它们各自有一个请求队列,那就是内存缓冲区了。做这项工作的框架叫做消息队列。

二、生产者与消费者实现

下面通过生产包子的例子及wait()/notify()方式实现该模型(后面学习线程池相关内容之后,再通过其它方式实现生产/者消费者模型)。

面包类:

public class Bread {
private int capacity; // 面包的容量
private int size; // 面包的实际数量
public Bread(int capacity) {
this.capacity = capacity;
this.size = 0;
} // 生产面包
public synchronized void produce(int val) {
try {
// left 表示“想要生产的数量”(有可能生产量太多,需多此生产)
int left = val;
while (left > 0) {
// 库存已满时,等待“消费者”消费产品。
while (size >= capacity)
wait();
// 获取“实际生产的数量”(即库存中新增的数量)
// 如果“库存”+“想要生产的数量”>“总的容量”,则“实际增量”=“总的容量”-“当前容量”。(此时填满仓库)
// 否则“实际增量”=“想要生产的数量”
int inc = (size+left)>capacity ? (capacity-size) : left;
size += inc;
left -= inc;
System.out.printf("%s produce(%3d) --> left=%3d, inc=%3d, size=%3d\n",
Thread.currentThread().getName(), val, left, inc, size);
// 通知“消费者”可以消费了。
notifyAll();
}
}catch(InterruptedException e) {
e.printStackTrace();
}
}
// 消费面包
public synchronized void consume(int val) {
try {
// left 表示“客户要消费数量”(有可能消费量太大,库存不够,需多此消费)
int left = val;
while (left > 0) {
// 库存为0时,等待“生产者”生产产品。
while (size <= 0)
wait();
// 获取“实际消费的数量”(即库存中实际减少的数量)
// 如果“库存”<“客户要消费的数量”,则“实际消费量”=“库存”;
// 否则,“实际消费量”=“客户要消费的数量”。
int dec = (size<left) ? size : left;
size -= dec;
left -= dec;
System.out.printf("%s consume(%3d) <-- left=%3d, dec=%3d, size=%3d\n",
Thread.currentThread().getName(), val, left, dec, size);
notifyAll();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

生产者类

public class Producer{
Bread bread;
public Producer(Bread bread) {
this.bread = bread;
}
public void produce(final int val) {
new Thread(() -> {
bread.produce(val);
}).start();;
}
}

消费者类

public class Customer {
private Bread bread;
public Customer(Bread bread) {
this.bread = bread;
}
public void consume(final int val) {
new Thread(() -> {
bread.consume(val);
}).start();;
}
}

测试类代码

public class Demo {
public static void main(String[] args) {
Bread bread = new Bread(100);
Producer producer = new Producer(bread);
Cunstomer customer = new Customer(bread); producer.produce(60);
producer.produce(120);
consumer.consume(90);
consumer.consume(150);
producer.produce(110);
}
}
// 运行结果
Thread-1 produce( 60) --> left= 0, inc= 60, size= 60
Thread-5 produce(110) --> left= 70, inc= 40, size=100
Thread-4 consume(150) <-- left= 50, dec=100, size= 0
Thread-2 produce(120) --> left= 20, inc=100, size=100
Thread-3 consume( 90) <-- left= 0, dec= 90, size= 10
Thread-4 consume(150) <-- left= 40, dec= 10, size= 0
Thread-5 produce(110) --> left= 0, inc= 70, size= 70
Thread-4 consume(150) <-- left= 0, dec= 40, size= 30
Thread-2 produce(120) --> left= 0, inc= 20, size= 50

说明:

①、Producer是“生产者”类,它与“面包(bread)”关联。当调用“生产者”的produce()方法时,它会新建一个线程并向“面包类”中生产产品。
②、Customer是“消费者”类,它与“面包(bread)”关联。当调用“消费者”的consume()方法时,它会新建一个线程并消费“面包类”中的产品。
③、Bread是面包类,记录“面包的产量(capacity)”以及面包当前实际数目(size)”。
        面包类的生产方法produce()和消费方法consume()方法都是synchronized方法,进入synchronized方法体,意味着这个线程获取到了该“面包”对象的同步锁。这也就是说,同一时间,生产者和消费者线程只能有一个能运行。通过同步锁,实现了对“残酷”的互斥访问。
       对于生产方法 produce() 而言:当面包量满时,生产者线程等待,需要等待消费者消费产品之后,生产线程才能生产;生产者线程生产完面包之后,会通过 notifyAll() 唤醒同步锁上的所有线程,包括“消费者线程”,即我们所说的“通知消费者进行消费”。
      对于消费方法consume()而言:当仓库为空时,消费者线程等待,需要等待生产者生产产品之后,消费者线程才能消费;消费者线程消费完产品之后,会通过 notifyAll() 唤醒同步锁上的所有线程,包括“生产者线程”,即我们所说的“通知生产者进行生产”。

Java 多线程基础(十二)生产者与消费者的更多相关文章

  1. “全栈2019”Java多线程第二十二章:饥饿线程(Starvation)详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  2. “全栈2019”Java多线程第十二章:后台线程setDaemon()方法详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  3. windows多线程(十) 生产者与消费者问题

    一.概述 生产者消费者问题是一个著名的线程同步问题,该问题描述如下:有一个生产者在生产产品,这些产品将提供给若干个消费者去消费,为了使生产者和消费者能并发执行,在两者之间设置一个具有多个缓冲区的缓冲池 ...

  4. JAVA多线程基础学习二:synchronized

    本篇主要介绍Java多线程中的同步,也就是如何在Java语言中写出线程安全的程序,如何在Java语言中解决非线程安全的相关问题,没错就是使用synchronized. 一.如何解决线程安全问题? 一般 ...

  5. JAVA多线程提高十二:阻塞队列应用

    一.类相关属性 接口BlockingQueue<E>定义: public interface BlockingQueue<E> extends Queue<E> { ...

  6. Java 多线程通信之多生产者/多消费者

    // 以生产和消费烤鸭为例 class Resource { private String name; private int count = 1; // 记录烤鸭的编号 private boolea ...

  7. Java多线程基础(二)

    1.多线程数据安全 线程同步:多个线程需要访问同一资源时,需要以某种顺序来确定该资源某一时刻只能被一个线程使用.从而,解决并发操作可能带来的异常. 2.同步代码块实现同步(部分代码的访问,我们希望它同 ...

  8. JAVA多线程学习十二 - Semaphere同步工具

    java 中Semaphere可类比操作系统信号量,硬件资源如IO.内存.磁盘等都是有固定量的,多个程序需要竞争这些资源,没有资源就需要被挂起. 一.类和方法摘要 构造函数: public Semap ...

  9. Java多线程(十二):中断机制

    这里详细分析interrupt(),interrupted(),isInterrupted()三个方法 interrupt() 中断这个线程,设置中断标识位 public void interrupt ...

  10. java多线程基础(二)--java线程各状态关系

    注意只有可运行(就绪态)和运行中(运行态)可以相互转换

随机推荐

  1. Unity 游戏框架搭建 2019 (五十六/五十七) 需求分析-架构中最重要的一环&从 EmptyGO 到 Manager Of Managers

    我们的项目开始立项的时候,最常见的一个情况就是:几个人的小团队,一开始什么也不做,就开始写代码,验证逻辑,游戏就开始写起来了.而公司的一些所谓的领导层面一开始就把游戏定义为我们要做一个大作.这个事情本 ...

  2. Java实现 LeetCode 453 最小移动次数使数组元素相等

    453. 最小移动次数使数组元素相等 给定一个长度为 n 的非空整数数组,找到让数组所有元素相等的最小移动次数.每次移动可以使 n - 1 个元素增加 1. 示例: 输入: [1,2,3] 输出: 3 ...

  3. Java实现 蓝桥杯VIP 基础练习 分解质因数

    题目介绍 问题描述 求出区间[a,b]中所有整数的质因数分解. 输入格式 输入两个整数a,b. 输出格式 每行输出一个数的分解,形如k=a1a2a3-(a1<=a2<=a3-,k也是从小到 ...

  4. Java实现 蓝桥杯VIP 基础练习 高精度加法

    java算法 蓝桥杯 高精度加法 问题描述 在C/C++语言中,整型所能表示的范围一般为-231到231(大约21亿),即使long long型,一般也只能表示到-263到263.要想计算更加规模的数 ...

  5. Java实现【USACO】1.1.2 贪婪的礼物送礼者 Greedy Gift Givers

    [USACO]1.1.2 贪婪的礼物送礼者 Greedy Gift Givers 题目描述 对于一群要互送礼物的朋友,你要确定每个人送出的礼物比收到的多多少(and vice versa for th ...

  6. Java实现 LeetCode 43 字符串相乘

    43. 字符串相乘 给定两个以字符串形式表示的非负整数 num1 和 num2,返回 num1 和 num2 的乘积,它们的乘积也表示为字符串形式. 示例 1: 输入: num1 = "2& ...

  7. html页面引用video.js播放m3u8格式视频

    //head里面的内容,我是采用cdn引用的方式,因为项目太小 <head> <meta charset="utf-8" /> <title>二 ...

  8. python自学Day05(自学书籍python编程从入门到实践)

    第6章 字典 6.1 一个简单的字典 先跟随书本创建一个简单的字典感受一下. alien_0 = {'color':'green','points':5} print(alien_0['color'] ...

  9. C# ASP.NET递归循环生成嵌套json结构树

    1. 建立用来保存树结构数据的目标对象 public class TreeObject { public string name { get; set; } public string value { ...

  10. Java—JDBC向mysql数据库中给某个表添加数据时,会遇到的问题,如下

    解析(jar包该放在那里,以及其它的操作): 把jar包(驱动)添加到自己的项目中,最好新建一个文件夹,再把jar包(驱动包)添加到这个所新建的文件中 1.先建好自己的项目,再新建一个文件夹,如下: ...