Java 入门指南:Java 并发编程模式 —— 生产者-消费者模式

文章目录

    • 生产者-消费者问题
      • 解决方案
    • 生产者-消费者模式
      • 模式的核心问题
      • 基本原理
        • 生产者
        • 消费者
      • 优点
      • 实现方式
        • 使用阻塞队列
          • 示例代码
        • 使用 `wait/notify` 机制
          • wait()
          • notify()
          • notifyAll()
          • 示例代码
        • 使用 `Exchanger`
          • 示例代码
      • 应用场景
      • 总结

生产者-消费者问题

生产者消费者问题是一个经典的并发编程问题,它涉及到多个线程之间共享资源的同步和互斥访问。

在生产者消费者问题中,有两类线程:生产者和消费者。生产者线程负责生产产品并将其放入一个共享的缓冲区,而消费者线程从缓冲区中取出产品并进行消费。但是,缓冲区有限,当缓冲区满时,生产者需要等待;当缓冲区空时,消费者需要等待。

解决方案

为了解决生产者消费者问题中的竞态条件和死锁等并发问题,常见的解决方案有以下几种:

  1. 使用互斥锁和条件变量:生产者和消费者共享一个互斥锁和两个条件变量,通过锁保护共享资源的访问,生产者线程在缓冲区满时等待,消费者线程在缓冲区为空时等待,从而实现线程之间的同步。

  2. 使用信号量:使用两个信号量来表示缓冲区的空和满状态,生产者在缓冲区满时等待,消费者在缓冲区为空时等待,通过对信号量的 P(原语)和 V(原语)操作来实现同步和互斥。

  3. 使用阻塞队列:可以使用具备线程安全的阻塞队列作为缓冲区,这样生产者可以直接将产品放入队列,消费者可以直接从队列中取出产品,队列会自动处理线程之间的同步和互斥。

生产者-消费者模式

生产者消费者模式是一种常见的多线程设计模式,用于解决生产者和消费者之间的解耦和同步问题。

在该模式中,生产者负责生产数据并将其放入共享的缓冲区,而消费者则负责从缓冲区中取出数据进行消费。通过合理地组织生产者和消费者线程的执行顺序和同步操作,可以有效地平衡生产和消费的速度,避免数据竞争和阻塞问题。

模式的核心问题

生产者-消费者模式的关键在于生产者和消费者之间的协调和同步,以确保以下几点:

  1. 生产者在缓冲区满时需要等待:以避免向缓冲区添加数据导致溢出。
  2. 消费者在缓冲区空时需要等待:以避免尝试从空缓冲区中取出数据。
  3. 生产者向缓冲区添加数据后需要唤醒等待中的消费者线程
  4. 消费者从缓冲区取出数据后需要唤醒等待中的生产者线程

基本原理

生产者/消费者模式的核心在于使用一个共享的队列来存储数据,这个队列可以是阻塞队列(BlockingQueue)或者是非阻塞队列(如 LinkedList)。队列的作用是在生产者和消费者之间传递数据,从而实现线程间的解耦。

生产者

生产者线程负责生成数据并将数据放入队列中。生产者的任务通常是数据的采集、计算或者是任何生成数据的操作。生产者在将数据放入队列时,必须确保队列不会溢出。如果队列已满,生产者可能需要等待,直到队列中有空闲的空间。

消费者

消费者线程负责从队列中取出数据并对其进行处理。消费者的任务通常是数据的消费、处理或者是任何使用数据的操作。消费者在从队列中取出数据时,必须确保队列不是空的。如果队列为空,消费者可能需要等待,直到队列中有新的数据可用。

优点

  • 解耦:生产者和消费者之间通过共享队列通信,而不是直接通信,这样就实现了生产者和消费者之间的解耦。生产者不知道也不关心数据会被哪个消费者消费,同样,消费者也不知道数据是由哪个生产者产生的。

  • 通过使用队列,生产者/消费者模式可以平滑负载,即使在短时间内有大量的数据需要处理,队列也可以暂时存储这些数据,防止生产者因为无法立即处理数据而导致的问题。

  • 生产者/消费者模式允许多个生产者和消费者同时工作,提高了系统的并发性。此外,由于队列的存在,生产者和消费者的数量可以灵活调整,以适应不同的工作负载。

实现方式

使用阻塞队列

Java 提供了 BlockingQueue 接口以及其实现类(如 ArrayBlockingQueueLinkedBlockingQueuePriorityBlockingQueue),可以直接用于实现生产者/消费者模式。使用阻塞队列的好处是可以简化线程间的同步逻辑,因为 BlockingQueue 本身提供了线程安全的阻塞方法(如 put()take())。

示例代码
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;public class ProducerConsumerUsingBlockingQueue {private static final BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);public static void main(String[] args) {Thread producerThread = new Thread(() -> {for (int i = 0; i < 100; i++) {try {queue.put(i);System.out.println("Produced: " + i);} catch (InterruptedException e) {Thread.currentThread().interrupt();}}});Thread consumerThread = new Thread(() -> {while (true) {try {int value = queue.take();System.out.println("Consumed: " + value);} catch (InterruptedException e) {Thread.currentThread().interrupt();}}});producerThread.start();consumerThread.start();}
}
使用 wait/notify 机制

如果不使用阻塞队列,可以通过手动实现 wait/notify 机制来控制生产者和消费者之间的同步。这种方式更加灵活,但同时也增加了实现的复杂度。

Java 入门指南:Java 并发编程 —— Condition 灵活管理线程间的同步
在这里插入图片描述

wait()

该方法用来将当前线程置入休眠状态,直到接到通知或被中断为止。

在调用 wait 之前,线程必须获得该对象的监视器锁,即只能在同步方法或同步块中调用 wait 方法。调用 wait 方法之后,当前线程会释放锁。如果调用 wait 方法时,线程并未获取到锁的话,则会抛出 IllegalMonitorStateException 异常。如果再次获取到锁的话,当前线程才能从 wait 方法处成功返回。

notify()

该方法也需要在同步方法或同步块中调用,即在调用前,线程也必须获得该对象的对象级别锁,如果调用 notify 时没有持有适当的锁,也会抛出 IllegalMonitorStateException

该方法会从 WAITTING 状态的线程中挑选一个进行通知,使得调用 wait 方法的线程从等待队列移入到同步队列中,等待机会再一次获取到锁,从而使得调用 wait 方法的线程能够从 wait 方法处退出。

调用 notify 后,当前线程不会马上释放该对象锁,要等到程序退出同步块后,当前线程才会释放锁。

notifyAll()

该方法与 notify 方法的工作方式相同,重要的一点差异是:notifyAll 会使所有原来在该对象上 wait 线程统统退出 WAITTING 状态,使得他们全部从等待队列中移入到同步队列中去,等待下一次获取到对象监视器锁的机会。

示例代码
import java.util.LinkedList;public class ProducerConsumerUsingWaitNotify {private static final LinkedList<Integer> queue = new LinkedList<>();private static final int MAX_SIZE = 10;private static final Object lock = new Object();public static void main(String[] args) {Thread producerThread = new Thread(() -> {for (int i = 0; i < 100; i++) {synchronized (lock) {while (queue.size() >= MAX_SIZE) {try {lock.wait();} catch (InterruptedException e) {Thread.currentThread().interrupt();}}queue.add(i);System.out.println("Produced: " + i);lock.notifyAll();}}});Thread consumerThread = new Thread(() -> {while (true) {synchronized (lock) {while (queue.isEmpty()) {try {lock.wait();} catch (InterruptedException e) {Thread.currentThread().interrupt();}}int value = queue.removeFirst();System.out.println("Consumed: " + value);lock.notifyAll();}}});producerThread.start();consumerThread.start();}
}
使用 Exchanger

对于需要成对交换数据的情况,可以使用 ExchangerExchanger 允许两个线程交换数据,当一个线程调用 exchange() 方法时,它会等待另一个线程也调用 exchange() 方法,然后两个线程可以交换它们的数据。

示例代码
import java.util.concurrent.Exchanger;public class ProducerConsumerUsingExchanger {private static final Exchanger<String> exchanger = new Exchanger<>();public static void main(String[] args) {Thread producerThread = new Thread(() -> {try {String value = "Data from Producer";String received = exchanger.exchange(value);System.out.println("Producer received: " + received);} catch (InterruptedException e) {Thread.currentThread().interrupt();}});Thread consumerThread = new Thread(() -> {try {String value = "Data from Consumer";String received = exchanger.exchange(value);System.out.println("Consumer received: " + received);} catch (InterruptedException e) {Thread.currentThread().interrupt();}});producerThread.start();consumerThread.start();}
}

应用场景

生产者/消费者模式可以应用于多种场景,以下是一些常见的应用场景:

  • 数据流处理系统中,生产者线程负责收集数据,而消费者线程负责处理数据。这种模式非常适合实时数据分析、日志处理等领域。

  • 图形渲染引擎中,生产者线程负责渲染图像帧,而消费者线程负责显示图像帧。这种模式可以提高渲染速度并减少延迟。

  • 消息队列系统:在消息队列系统中,生产者线程负责发布消息,而消费者线程负责接收消息。这种模式广泛应用于分布式系统中的消息传递。

总结

生产者/消费者模式是一种重要的多线程设计模式,它通过引入共享队列来实现生产者和消费者之间的解耦。这种模式不仅可以提高系统的并发性和灵活性,还可以平滑负载,适用于多种应用场景。通过使用阻塞队列、wait/notify 机制或者 Exchanger,可以方便地实现生产者/消费者模式,并解决多线程环境下数据生产和消费的问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/423070.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JS - 获取剪切板内容 Clipboard API

目录 1&#xff0c;需求最终效果 2&#xff0c;实现示例 3&#xff0c;注意点1&#xff0c;只支持安全上下文环境2&#xff0c;只能读取当前页面的剪切板3&#xff0c;权限获取问题4&#xff0c;获取内容的 MIME_TYPE 问题1&#xff0c;文本内容2&#xff0c;图片内容 5&#x…

3.C++入门(内联函数,c++11,auto,范围for,nullptr)

⭐本篇文章为C学习的第三篇&#xff1a;主要了解内联函数和部分c11新特性 ⭐本人c代码的Gitee仓库&#xff1a;c学习 橘子真甜/yzc的c学习 - 码云 - 开源中国 (gitee.com) 一. 内联函数 以inline修饰的函数称为内联函数&#xff0c;编译的时候c编译器会在内联函数的地方展开&a…

【GBase 8c V5_3.0.0 分布式数据库常用维护命令】

一、查看数据库状态/检查&#xff08;gbase用户&#xff09; 1.gha_ctl monitor 使用gha_ctl monitor查看节点运行情况(跟dcs的地址和端口) gha_ctl monitor -c gbase -l http://172.20.10.8:2379 -Hall |coordinator | datanode | gtm | server|dcs:必选字段。指定查看哪类集…

【有啥问啥】探索扫地机器人中的 SLAM 算法:原理、实现与未来展望

探索扫地机器人中的 SLAM 算法&#xff1a;原理、实现与未来展望 随着智能家居的普及&#xff0c;扫地机器人逐渐成为日常生活中的常见家电。其自主导航能力使得它能够在复杂的家庭环境中高效完成清洁任务&#xff0c;而这背后的核心技术之一就是 SLAM&#xff08;Simultaneou…

【文件包含】——日志文件注入

改变的确很难&#xff0c;但结果值得冒险 本文主要根据做题内容的总结&#xff0c;如有错误之处&#xff0c;还请各位师傅指正 一.伪协议的失效 当我们做到关于文件包含的题目时&#xff0c;常用思路其实就是使用伪协议&#xff08;php:filter,data,inpput等等&#xff09;执行…

shader 案例学习笔记之step函数

step函数 参数是float step(edge,x)&#xff1a;当x>edge时返回1&#xff0c;否则返回0 #ifdef GL_ES precision mediump float; #endifuniform vec2 u_resolution;void main(){vec2 st gl_FragCoord.xy/u_resolution.xy;float f step(0.5, st.x);gl_FragColor vec4(f…

算法知识点————数论【最大公约数】【快速幂】【分解质因数】

结论1&#xff1a;两个互质的整数mn不能凑出的最大整数是(n-1)(m-1) -1 结论2:一个数的因数可以拆成n个质因数的乘积。 黄金分割&#xff1a;0.61803399 在数论中&#xff0c;如果两个或两个以上的整数的最大公约数是 1 &#xff0c;则称它们为互质。 最大公约数: 两数乘积最…

C语言:结构体

在前面我们已经介绍了整形&#xff0c;浮点型&#xff0c;字符型&#xff0c;还介绍了数组&#xff0c;字符串。但是在实际问题中只有这些数据类型是不够的&#xff0c;有时候我们需要其中的几种一起来修饰某个变量&#xff0c;例如一个学生的信息就需要学号&#xff08;字符串…

基础 Web 开发

1. 构建项目&#xff1a; 2.添加依赖 <dependencies> <dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupI…

[ RK3566-Android11 ] 关于 RK628F 驱动移植以及调试说明

问题描述 我这个项目的SDK比较老&#xff0c;移植RK628F最新驱动的调试过程&#xff0c;踩了很多坑&#xff0c;希望大家别踩坑。 解决方案&#xff1a; 首先在FTP上下载最新的RK628的驱动 rk628-for-all-v27-240730 版本。 下载完后 不要直接替换&#xff0c;不要直接替换&a…

网络高级(学习)2024.9.10

目录 一、Modbus简介 1.起源 2.特点 3.应用场景 二、Modbus TCP协议 1.特点 2.协议格式 3.MBAP报文头 4.功能码 5.寄存器 &#xff08;1&#xff09;线圈寄存器&#xff0c;类比为开关量&#xff0c;每一个bit都对应一个信号的开关状态。 &#xff08;2&#xff09…

中学生考试成绩在线查询系统

时代在发展&#xff0c;社会在进步&#xff0c;传统的成绩发布方式已经显得力不从心了。老师们&#xff0c;是时候尝试一种更高效、更安全的成绩查询方式了。 还在为如何保护学生隐私而头疼&#xff1f;还在担心成绩的公平性和准确性&#xff1f;易查分小程序将这些这些问题都将…

安卓13禁止声音调节对话框 删除音量调节对话框弹出 屏蔽音量对话框 android13

总纲 android13 rom 开发总纲说明 文章目录 1.前言2.问题分析3.代码分析3.1 方法13.2 方法24.代码修改4.1 代码修改方法14.2 代码修改方法25.编译6.彩蛋1.前言 客户需要,调整声音,不显示声音调节对话框了。我们在系统里面隐藏这个对话框。 2.问题分析 android在调整声音的…

部署Tomcat和抓包

部署Tomcat 复制文件到桌面 查看自己是否有java环境&#xff0c;下图所示是有的&#xff0c;若没有需另行下载 解压tomcat文件 tar -xzvf apache-tomcat-7.0.96.tar.gz 下列为tomcat文件的几个重要文件 进入到bin文件中 启动tomcat ./startup.sh 可以先用本机查看是否启动…

戴尔14代服务器配置IDRAC9远程配置说明

一、规划管理网段 规划管理网段&#xff0c;要求如下&#xff1a; 管理网段与业务网段不能使用同一网段&#xff1b;管理网段与业务网段不能直接互通&#xff1b;如有条件管理网与业务网使用不同设备接入。 二、配置服务器idrac 2.1、确认idrac口位置 2.2、开机进F2 2.3、 …

java程序员入行科目一之CRUD轻松入门教程(一)

之前在操作MySQL的时候&#xff0c;都是采用Navicat&#xff0c;或者cmd黑窗口。 无论使用什么方式和MySQL交互&#xff0c;大致步骤是这样的 建立连接&#xff0c;需要输入用户名和密码编写SQL语句&#xff0c;和数据库进行交互 这个连接方式不会变&#xff0c;但是现在需要 基…

(学习总结16)C++模版2

C模版2 一、非类型模板参数二、模板的特化1. 概念2. 函数模板特化3. 类模板特化全特化偏特化类模板特化应用示例 三、模板分离编译1. 什么是分离编译2. 模板的分离编译3. 解决方法 模板总结 以下代码环境为 VS2022 C。 一、非类型模板参数 模板参数分为类型形参与非类型形参。…

什么是CPU、GPU、NPU?(包懂+会)

目录 举例子 CPU&#xff1a;主厨 GPU&#xff1a;大量的厨房助理 NPU&#xff1a;面包机 总结 讲理论 CPU&#xff08;中央处理器&#xff09; GPU&#xff08;图形处理单元&#xff09; NPU&#xff08;神经网络处理单元&#xff09; 对比分析 举例子 CPU&#xff…

【代码随想录训练营第42期 Day55打卡 - 图论Part5 - 并查集的应用

目录 一、并查集 适用范围 三大基本操作 二、经典题目 题目&#xff1a;卡码网 107. 寻找存在的路径 题目链接 题解&#xff1a;并查集 三、小结 一、并查集 适用范围 动态连通性问题&#xff1a;并查集可以判断两个节点是否在同一个连通分量中&#xff0c;这在处理网络…

C语言——模拟实现strcat

strcat的作用是实现字符串的连接或者追加的 还是老样子我们先学会strcat的使用方式 int main() {char arr[30] "hello ";strcat(arr, "world");printf("%s", arr);return 0; } 库函数的规则了解了&#xff0c;那跟着之前讲过strcpy的逻辑改写。…