探索生产者/消费者模式:解决并发编程中的资源竞争

news2024/11/16 22:40:01

序言

在并发编程中,资源竞争是一个常见的问题。为了有效地管理资源并确保线程安全,需要采用一些有效的方法。其中之一是生产者/消费者模式,它是一种经典的并发设计模式,用于解决生产者和消费者之间的协作问题。本文将深入探讨生产者/消费者模式的概念、应用场景以及实现方法。

一、什么是生产者/消费者模式

生产者/消费者模式是一种并发设计模式,用于解决多线程环境下的资源共享与同步问题。它涉及两种类型的线程:生产者和消费者。生产者负责生成数据或任务,并将它们放入共享的缓冲区中,而消费者则负责从缓冲区中取出数据或任务并进行处理。

二、应用场景

生产者/消费者模式在许多实际场景中都有广泛的应用,其中包括但不限于:

  1. 生产者/消费者问题:在计算机科学中,生产者/消费者问题是一个经典的问题,涉及到多个生产者和消费者并共享一个有限大小的缓冲区。生产者生成数据并将其放入缓冲区,消费者则从缓冲区中取出数据并进行处理。生产者和消费者之间必须进行同步,以确保缓冲区不会溢出或下溢。
  2. 线程池:线程池是一种常见的并发编程模式,用于管理和复用线程。生产者负责将任务提交给线程池,而线程池中的线程则充当消费者,负责执行这些任务。
  3. 事件驱动编程:在事件驱动编程中,事件生成者(生产者)生成事件并将其放入事件队列中,而事件处理程序(消费者)则从队列中取出事件并处理它们。

四、实现方法

在实现生产者/消费者模式时,有几种常见的方法:

  1. 使用线程和共享缓冲区:这是最直接的实现方法。生产者线程生成数据并将其放入共享的缓冲区,而消费者线程则从缓冲区中取出数据。需要注意的是,对于共享缓冲区的访问需要进行同步,以防止竞争条件和数据不一致性。
  2. 使用阻塞队列:阻塞队列是一种线程安全的队列数据结构,它支持在队列为空时阻塞消费者线程,并在队列已满时阻塞生产者线程。通过使用阻塞队列,可以简化生产者/消费者模式的实现,并减少竞争条件的发生。
  3. 使用信号量:信号量是一种并发原语,用于控制对共享资源的访问。生产者和消费者可以使用信号量来进行同步,以确保缓冲区的访问不会发生冲突。

五、使用案例

场景假设:我们正在开发一个在线商店系统,其中有一个订单管理模块。在这个模块中,订单被创建并放入一个订单队列,然后由后台工作人员逐个处理

  1. 定义实体类:表示订单

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Order {
        private int orderId; // 订单 id
        private String customerName; // 顾客姓名
    }
    
  2. 定义生产者:不断生成新的订单

    public class OrderProducer implements Runnable {
    
        private final Queue<Order> orderQueue; // 订单队列
        private final int maxQueueSize; // 订单队列最大容量
        private int orderNumber; // 订单编号
    
        public OrderProducer(Queue<Order> orderQueue, int maxQueueSize) {
            this.orderQueue = orderQueue;
            this.maxQueueSize = maxQueueSize;
            // 初始订单编号为 1
            this.orderNumber = 1;
        }
    
        @Override
        public void run() {
            while (true) {
                synchronized (orderQueue) {
                    // 当订单队列满时等待
                    while (orderQueue.size() == maxQueueSize) {
                        try {
                            System.out.println("Order queue is full, waiting for orders to be processed...");
                            // 等待订单队列有空间
                            orderQueue.wait();
                        } catch (InterruptedException e) {
                            System.out.println("处理生产者异常");
                        }
                    }
    
                    // 创建新订单并添加到订单队列中
                    Order order = new Order(orderNumber++, "Customer " + orderNumber);
                    orderQueue.add(order);
                    System.out.println("New order added: Order #" + order.getOrderId() + " by " + order.getCustomerName());
                    // 通知订单处理者有新订单
                    orderQueue.notifyAll();
                }
            }
        }
    }
    
  3. 定义消费者:不断处理新订单

    public class OrderConsumer implements Runnable {
        // 订单队列
        private final Queue<Order> orderQueue;
    
        public OrderConsumer(Queue<Order> orderQueue) {
            this.orderQueue = orderQueue;
        }
    
        @Override
        public void run() {
            while (true) {
                synchronized (orderQueue) {
                    // 当订单队列为空时等待
                    while (orderQueue.isEmpty()) {
                        try {
                            System.out.println("No orders in the queue, waiting for new orders...");
                            // 等待新订单
                            orderQueue.wait();
                        } catch (InterruptedException e) {
                            System.out.println("处理消费者异常");
                        }
                    }
    
                    // 从订单队列中取出订单并处理
                    Order order = orderQueue.poll();
                    System.out.println("Order processed: Order #" + order.getOrderId() + " by " + order.getCustomerName());
                    // 通知订单生产者有空间
                    orderQueue.notifyAll();
                }
            }
        }
    }
    
  4. 测试生产者/消费者模式

    public static void main(String[] args) {
        Queue<Order> orderQueue = new LinkedList<>(); // 订单队列
        int maxQueueSize = 10; // 订单队列最大容量
    
        OrderProducer orderProducer = new OrderProducer(orderQueue, maxQueueSize); // 订单生产者
        OrderConsumer orderProcessor = new OrderConsumer(orderQueue); // 订单处理者
    
        Thread producerThread = new Thread(orderProducer); // 订单生产者线程
        Thread processorThread = new Thread(orderProcessor); // 订单处理者线程
    
        producerThread.start(); // 启动订单生产者线程
        processorThread.start(); // 启动订单处理者线程
    }
    

    测试效果:

    image.png

六、FAQ

生产者/消费者模式是一种有效的并发设计模式,用于解决资源共享与同步的问题。通过合理地设计和实现生产者和消费者之间的协作,可以提高系统的性能和可靠性,同时减少竞争条件和数据不一致性的发生。在实际应用中,可以根据具体的场景选择合适的实现方法,并结合其他并发编程技术来构建高效、可靠的并发系统。

推荐阅读

  1. 深入探究 Spring Boot Starter:从概念到实践
  2. RBAC 权限设计(五)
  3. Docker Compose:简化多容器应用部署
  4. cURL:命令行下的网络工具
  5. RabbitMQ(Docker 单机部署)

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

零代码平台助力中国石化江苏油田实现高效评价体系

概述&#xff1a; 中国石化集团江苏石油勘探局有限公司面临着评价体系依赖人工处理数据、计算繁琐且容易出错的挑战。为解决这一问题&#xff0c;他们决定借助零代码平台明道云开发江苏油田高质量发展经济指标评价系统。该系统旨在实现原始数据批量导入与在线管理、权重及评分…

27.哀家要长脑子了!---栈与队列

1.739. 每日温度 - 力扣&#xff08;LeetCode&#xff09; 用单调栈的方法做&#xff1a; 从左到右遍历数组&#xff1a; 栈中存放的是下标&#xff0c;每个温度在原数组中的下标&#xff0c;从大到小排列&#xff0c;因为这样才能确保的是最近一天的升高温度 如果栈为空&am…

Linux线程(二)线程互斥

目录 一、为什么需要线程互斥 二、线程互斥的必要性 三、票务问题举例&#xff08;多个线程并发的操作共享变量引发问题&#xff09; 四、互斥锁的用法 1.互斥锁的原理 2、互斥锁的使用 1、初始化互斥锁 2、加锁和解锁 3、销毁互斥锁&#xff08;动态分配时需要&#…

程序员代码面试指南题目解析(一)

题目一&#xff1a;如何仅用递归函数和栈操作逆序一个栈 题目要求&#xff1a; 一个栈依次压入 1、2、3、4、5&#xff0c;那么从栈顶到栈底分别为5、4、3、2、1。将这个栈 转置后&#xff0c;从栈顶到栈底为 1、2、3、4、5&#xff0c;也就是实现栈中元素的逆序&#xff0c;但…

JUC下的BlockingQueue详解

BlockingQueue是Java并发包(java.util.concurrent)中提供的一个接口&#xff0c;它扩展了Queue接口&#xff0c;增加了阻塞功能。这意味着当队列满时尝试入队操作&#xff0c;或者队列空时尝试出队操作&#xff0c;线程会进入等待状态&#xff0c;直到队列状态允许操作继续。这…

【Python系列】Python中列表属性提取

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

AI菜鸟向前飞 — 大模型基础知识篇

前言 主要介绍最最基础的知识&#xff0c;在这个基础上有现在比较流行的GPT、Llama、Gemini**等一系列大模型的出现&#xff0c;打好基础才能更理解上面是如何运作以及实现的过程。 PS&#xff1a;本篇科普不会介绍梯度下降算法&#xff08;偏导数&#xff09;等复杂的过程&a…

巨坑啊! before-upload返回false 会执行on-remove

通过对on-remove对应参数的打印&#xff0c;发现回调中的file参数有个status&#xff0c;若是是在before-upload中就被过滤了&#xff0c;就是ready&#xff0c;若是已经上传成功了去点击删除&#xff0c;status是success&#xff0c;就他了。 onRemove(file,fileList){if(file…

redis的双写一致性

双写一致性问题 1.先删除缓存或者先修改数据库都可能出现脏数据。 2.删除两次缓存&#xff0c;可以在一定程度上降低脏数据的出现。 3.延时是因为数据库一般采用主从分离&#xff0c;读写分离。延迟一会是让主节点把数据同步到从节点。 1.读写锁保证数据的强一致性 因为一般放…

Redis——缓存雪崩、缓存穿透、缓存击穿

在项目中&#xff0c;通常会使用数据库比如 MySQL 存储应用数据&#xff0c;但是当数据太多之后&#xff0c;比如多了几十万条或上百万条的商品信息&#xff0c;这个时候查询商品数据的速度会很慢&#xff0c;影响用户体验。此时一般我们会选择将部分商品信息缓存起来&#xff…

【Rollup】用rollup从0到1开发一个js插件并发布到npm

Rollup 是一个 JavaScript 模块打包器&#xff0c;专注于打包 ES6 模块将其编译回多种模块化格式&#xff0c;尤其适合打包库和框架&#xff0c;因为它可以生成更小、更高效的代码&#xff0c;并且特别适合将代码打包成可在浏览器中使用的库。 从0到1开发js插件 1.创建文件夹…

2.前端路由的配置和使用

一&#xff0c;路由的作用 路由的作用就是将页面文件跟URL地址形成对应匹配 二&#xff0c;如何安装路由 这里我们采用pnpm的方式在项目中执行 pnpm install vue-routernext --save三&#xff0c;路由如何使用 首先创建一个我们需要访问的页面文件&#xff0c;这里我先创建…

机器学习:葡萄酒品质预测

说明&#xff0c;此项目是我的期末大作业&#xff0c;包括了对数据集探索&#xff0c;预处理以及分类的各个详细过程与描述&#xff0c;代码简单&#xff0c;主要是一个分类项目的流程&#xff0c;并没有对模型进行深度研究&#xff0c;因此我写在这里。 目录 一、问题介绍 …

ETL工具kettle(PDI)入门教程,Job

先新建两个Transform&#xff0c;MysqlToMysql.ktr和CsvToExcel.ktrURL&#xff1a;ETL工具kettle入门教程&#xff0c;transform&#xff0c;Mysql-&#xff1e;Mysql&#xff0c;Csv-&#xff1e;Excel-CSDN博客 主对象树&#xff0c;作业上右击&#xff0c;点击新建 核心对…

Unity引擎是什么?有哪些优点

大家好&#xff0c;我是咕噜土豆&#xff0c;很高兴又和大家见面了。今天我们一起来了解一下Unity引擎和它有哪些优点。 首先带大家了解什么是Unity引擎 Unity引擎是一款由Unity Technologies开发的跨平台游戏开发引擎&#xff0c;广泛用于创建2D和3D游戏以及其他交互式内容&…

uniapp管理后台编写,基于uniadmin和vue3实现uniapp小程序的管理后台

一&#xff0c;创建uniAdmin项目 打开开发者工具Hbuilder,然后点击左上角的文件&#xff0c;点新建&#xff0c;点项目。如下图。 选择uniadmin&#xff0c;编写项目名&#xff0c;然后使用vue3 记得选用阿里云服务器&#xff0c;因为最便宜 点击创建&#xff0c;等待项目创…

AI跟踪报道第41期-新加坡内哥谈技术-本周AI新闻:本周Al新闻: 准备好了吗?事情即将変得瘋狂

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

在Spring Boot应用安装SSL证书

目录 前提条件 步骤一&#xff1a;下载SSL证书 步骤二&#xff1a;在Spring Boot安装SSL证书 步骤三&#xff1a;验证SSL证书是否安装成功 前提条件 已通过数字证书管理服务控制台签发证书SSL证书绑定的域名已完成DNS解析&#xff0c;即您的域名与主机IP地址相互映射已在W…

ASP.NET学生信息管理系统

摘 要 本文介绍了在ASP.net环境下采用“自上而下地总体规划&#xff0c;自下而上地应用开发”的策略开发一个管理信息系统的过程。通过分析某一学校学生管理的不足&#xff0c;创建了一套行之有效的计算机管理学生的方案。文章介绍了学生管理信息系统的系统分析部分&#xff0c…

【C++ | 函数】默认参数、哑元参数、函数重载、内联函数

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; ⏰发布时间⏰&#xff1a;2024-05-04 1…