热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

开发笔记:Java并发编程生产者与消费者模式介绍

篇首语:本文由编程笔记#小编为大家整理,主要介绍了Java并发编程--生产者与消费者模式介绍相关的知识,希望对你有一定的参考价值。一、前言

篇首语:本文由编程笔记#小编为大家整理,主要介绍了Java并发编程--生产者与消费者模式介绍相关的知识,希望对你有一定的参考价值。




一、前言

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

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


二、使用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

  

 



推荐阅读
  • 探索偶数次幂二项式系数的求和方法及其数学意义 ... [详细]
  • 通过使用CIFAR-10数据集,本文详细介绍了如何快速掌握Mixup数据增强技术,并展示了该方法在图像分类任务中的显著效果。实验结果表明,Mixup能够有效提高模型的泛化能力和分类精度,为图像识别领域的研究提供了有价值的参考。 ... [详细]
  • Python 实战:异步爬虫(协程技术)与分布式爬虫(多进程应用)深入解析
    本文将深入探讨 Python 异步爬虫和分布式爬虫的技术细节,重点介绍协程技术和多进程应用在爬虫开发中的实际应用。通过对比多进程和协程的工作原理,帮助读者理解两者在性能和资源利用上的差异,从而在实际项目中做出更合适的选择。文章还将结合具体案例,展示如何高效地实现异步和分布式爬虫,以提升数据抓取的效率和稳定性。 ... [详细]
  • 探索聚类分析中的K-Means与DBSCAN算法及其应用
    聚类分析是一种用于解决样本或特征分类问题的统计分析方法,也是数据挖掘领域的重要算法之一。本文主要探讨了K-Means和DBSCAN两种聚类算法的原理及其应用场景。K-Means算法通过迭代优化簇中心来实现数据点的划分,适用于球形分布的数据集;而DBSCAN算法则基于密度进行聚类,能够有效识别任意形状的簇,并且对噪声数据具有较好的鲁棒性。通过对这两种算法的对比分析,本文旨在为实际应用中选择合适的聚类方法提供参考。 ... [详细]
  • 捕获并处理用户输入数字时的异常,提供详细的错误提示与指导
    在用户输入数字时,程序能够有效捕获并处理各种异常情况,如非法字符或格式错误,并提供详尽的错误提示和操作指导,确保用户能够准确输入有效的数字数据。通过这种方式,不仅提高了程序的健壮性和用户体验,还减少了因输入错误导致的系统故障。具体实现中,使用了Java的异常处理机制,结合Scanner类进行输入读取和验证,确保了输入的合法性和准确性。 ... [详细]
  • 深入解析 Java UTC 时间处理技术与应用 ... [详细]
  • 在Python多进程编程中,`multiprocessing`模块是不可或缺的工具。本文详细探讨了该模块在多进程管理中的核心原理,并通过实际代码示例进行了深入分析。文章不仅总结了常见的多进程编程技巧,还提供了解决常见问题的实用方法,帮助读者更好地理解和应用多进程编程技术。 ... [详细]
  • 本文介绍了如何利用Apache POI库高效读取Excel文件中的数据。通过实际测试,除了分数被转换为小数存储外,其他数据均能正确读取。若在使用过程中发现任何问题,请及时留言反馈,以便我们进行更新和改进。 ... [详细]
  • 本文深入探讨了CGLIB BeanCopier在Bean对象复制中的应用及其优化技巧。相较于Spring的BeanUtils和Apache的BeanUtils,CGLIB BeanCopier在性能上具有显著优势。通过详细分析其内部机制和使用场景,本文提供了多种优化方法,帮助开发者在实际项目中更高效地利用这一工具。此外,文章还讨论了CGLIB BeanCopier在复杂对象结构和大规模数据处理中的表现,为读者提供了实用的参考和建议。 ... [详细]
  • 本文详细探讨了Zebra路由软件中的线程机制及其实际应用。通过对Zebra线程模型的深入分析,揭示了其在高效处理网络路由任务中的关键作用。文章还介绍了线程同步与通信机制,以及如何通过优化线程管理提升系统性能。此外,结合具体应用场景,展示了Zebra线程机制在复杂网络环境下的优势和灵活性。 ... [详细]
  • 利用Flask框架进行高效Web应用开发
    本文探讨了如何利用Flask框架高效开发Web应用,以满足特定业务需求。具体案例中,一家餐厅希望每天推出不同的特色菜,并通过网站向顾客展示当天的特色菜。此外,还增加了一个介绍页面,在bios路径下详细展示了餐厅主人、厨师和服务员的背景和简介。通过Flask框架的灵活配置和简洁代码,实现了这一功能,提升了用户体验和餐厅的管理水平。 ... [详细]
  • 如何高效启动大数据应用之旅?
    在前一篇文章中,我探讨了大数据的定义及其与数据挖掘的区别。本文将重点介绍如何高效启动大数据应用项目,涵盖关键步骤和最佳实践,帮助读者快速踏上大数据之旅。 ... [详细]
  • Java SE 文件操作类详解与应用
    ### Java SE 文件操作类详解与应用#### 1. File 类##### 1.1 File 类概述File 类是 Java SE 中用于表示文件和目录路径名的对象。它提供了丰富的方法来操作文件和目录,包括创建、删除、重命名文件,以及获取文件属性和信息。通过 File 类,开发者可以轻松地进行文件系统操作,如检查文件是否存在、读取文件内容、列出目录下的文件等。此外,File 类还支持跨平台操作,确保在不同操作系统中的一致性。 ... [详细]
  • 通过采用用户数据报协议(UDP),本研究设计并实现了一种高效的文件传输方法。在发送端,系统利用Java编程语言中的相关类库,如`File`和`FileInputStream`,实现了文件的读取与分段处理,确保了数据的快速传输。该方法不仅提高了传输效率,还降低了网络拥塞的风险,适用于大规模文件传输场景。 ... [详细]
  • 本文探讨了基于点集估算图像区域的Alpha形状算法在Python中的应用。通过改进传统的Delaunay三角剖分方法,该算法能够生成更加灵活和精确的形状轮廓,避免了单纯使用Delaunay三角剖分时可能出现的过大三角形问题。这种“模糊Delaunay三角剖分”技术不仅提高了形状的准确性,还增强了对复杂图像区域的适应能力。 ... [详细]
author-avatar
手机用户2602897411
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有