【Java多线程手写代码】(五):如何优雅的实现生产者、消费者

生产者-消费者模式:Java多线程通信的经典实践 本文探讨了多线程编程中的生产者-消费者模式及其在字符串拼接中的应用。首先分析了线程间通信的必要性及面临的竞态条件、内存可见性等挑战。然后详细介绍了Java通过synchronized、wait()、notify()等机制实现线程安全的方法。重点展示了改进版生产者-消费者模式的实现,包括共享队列、多生产者/消费者线程管理、优雅停止机制等。该方案采用BlockingQueue简化同步操作,支持输入终止信号,并通过StringBuilder线程安全地拼接字符串,为异步字符串处理提供了可靠解决方案。 阅读详情

我是少侠露飞。学习塑造人生,技术改变世界。

前言

生产者-消费者模式是一个十分经典的多线程并发协作的模式,弄懂生产者-消费者问题能够让我们对并发编程的理解加深。所谓生产者-消费者问题,实际上主要是包含了两类线程,一种是生产者线程用于生产数据,另一种是消费者线程用于消费数据,为了解耦生产者和消费者的关系,通常会有一个共享的数据区域,就像是一个仓库,生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为;而消费者只需要从共享数据区中去获取数据,就不再需要关心生产者的行为。但是,这个共享数据区域中应该具备这样的线程间并发协作的功能:

  1. 如果共享数据区已满的话,阻塞生产者继续生产数据放置入内;
  2. 如果共享数据区为空的话,阻塞消费者继续消费数据;

在实现生产者消费者问题时,可以采用三种方式:

  • 基于Object的wait/notify的消息通知机制。
  • 基于ReentrantLock 和Condition的await/signal的消息通知机制。
  • 基于BlockingQueue实现。

本文主要将对后两种实现方式进行总结归纳。

通过ReentrantLock 和Condition的await/signalAll机制实现

参照Object的wait和notify/notifyAll方法,Condition也提供了同样的方法:

针对wait方法

void await() throws InterruptedException:当前线程进入等待状态,如果其他线程调用condition的signal或者signalAll方法并且当前线程获取Lock从await方法返回,如果在等待状态中被中断会抛出被中断异常;

long awaitNanos(long nanosTimeout):当前线程进入等待状态直到被通知,中断或者超时;

boolean await(long time, TimeUnit unit) throws InterruptedException:同第二种,支持自定义时间单位

boolean awaitUntil(Date deadline) throws InterruptedException:当前线程进入等待状态直到被通知,中断或者到了某个时间

针对notify方法

void signal():唤醒一个等待在Condition上的线程,将该线程从等待队列中转移到同步队列中,如果在同步队列中能够竞争到Lock则可以从等待方法中返回。

void signalAll():与signal的区别在于能够唤醒所有等待在Condition上的线程。

public class Main {
    private static final ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("Main-%d").build();

    private static final ExecutorService executorService = new ThreadPoolExecutor(10, 10, 100L, TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<Runnable>(1024), threadFactory, new ThreadPoolExecutor.AbortPolicy());

    private static ReentrantLock lock = new ReentrantLock();
    private static Condition full = lock.newCondition();
    private static Condition empty = lock.newCondition();

    public static void main(String[] args) {
        LinkedList linkedList = new LinkedList();
        for (int i = 0; i < 5; i++) {
            executorService.submit(new Producer(linkedList, 8, lock));
        }
        for (int i = 0; i < 5; i++) {
            executorService.submit(new Consumer(linkedList, lock));
        }
    }

    static class Producer implements Runnable {
        private List<Integer> list;
        private int maxSize;
        private Lock lock;

        public Producer(List list, int maxSize, Lock lock) {
            this.list = list;
            this.maxSize = maxSize;
            this.lock = lock;
        }

        @Override
        public void run() {
            for (; ; ) {
                lock.lock();
                try {
                    while (list.size() >= maxSize) {
                        System.out.println("Producer " + Thread.currentThread().getName() + " has reached maximum size!");
                        full.await();
                    }
                    Random random = new Random();
                    Integer element = random.nextInt();
                    list.add(element);
                    System.out.println("Producer " + Thread.currentThread().getName() + "  produce message:" + element);
                    empty.signalAll();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        }
    }

    static class Consumer implements Runnable {
        private List<Integer> list;
        private Lock lock;

        public Consumer(List list, Lock lock) {
            this.list = list;
            this.lock = lock;
        }

        @Override
        public void run() {
            for (; ; ) {
                lock.lock();
                try {
                    while (list.isEmpty()) {
                        System.out.println("Consumer " + Thread.currentThread().getName() + " unable get message from empty queue!");
                        empty.await();
                    }
                    Integer element = list.remove(0);
                    System.out.println("Consumer " + Thread.currentThread().getName() + "  consume message:" + element);
                    full.signalAll();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        }
    }
}

通过BlockingQueue实现

由于BlockingQueue内部实现就附加了两个阻塞操作。即当队列已满时,阻塞向队列中插入数据的线程,直至队列中未满;当队列为空时,阻塞从队列中获取数据的线程,直至队列非空时为止。可以利用BlockingQueue实现生产者-消费者为题,阻塞队列完全可以充当共享数据区域,就可以很好的完成生产者和消费者线程之间的协作。

public class Main {
    private static final ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("Main-%d").build();

    private static final ExecutorService executorService = new ThreadPoolExecutor(10, 10, 100L, TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<Runnable>(1024), threadFactory, new ThreadPoolExecutor.AbortPolicy());

    private static LinkedBlockingQueue<Integer> queue = new LinkedBlockingQueue<>();

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            executorService.submit(new Producer(queue));
        }
        for (int i = 0; i < 5; i++) {
            executorService.submit(new Consumer(queue));
        }
    }

    static class Producer implements Runnable {
        private BlockingQueue queue;

        public Producer(BlockingQueue queue) {
            this.queue = queue;
        }

        @Override
        public void run() {
            try {
                for (; ; ) {
                    Random random = new Random();
                    int element = random.nextInt();
                    System.out.println("Producer " + Thread.currentThread().getName() + "  produce message:" + element);
                    queue.put(element);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    static class Consumer implements Runnable {
        private BlockingQueue queue;

        public Consumer(BlockingQueue queue) {
            this.queue = queue;
        }

        @Override
        public void run() {
            try {
                for (; ; ) {
                    Integer element = (Integer) queue.take();
                    System.out.println("Consumer " + Thread.currentThread().getName() + "  consume message:" + element);
                    Thread.sleep(1000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

在这里插入图片描述

Java实现生产者消费者问题详解与代码 本文还有配套的精品资源,点击获取 简介:生产者消费者问题作为操作系统中多线程编程的经典案例,涉及进程间同步与通信。Java利用其并发工具类,如 BlockingQueue ,提供了有效解决方案。本文提供了一个Java代码实现,通过生产者线程向缓冲区投放产品和消费者线程从缓冲区取走产品来阐述问题。详细解释了如何使用 put() 和 take... 阅读详情

相关推荐

STM32 USB AUDIO实战指南——从零构建音频设备

本文详细介绍了如何使用STM32F407构建USB音频设备,从硬件准备到软件配置,再到音频播放和录制功能的实现。通过STM32CubeMX配置和实战代码示例,帮助开发者快速掌握USB Audio Class规范,实现低延迟、高质量的音频传输,适用于语音交互、音乐播放等多种场景。

weixin_42549037的博客 484

Java同时执行多个线程(优雅的进行线程池创建)

欢迎访问我的个人博客:www.ifueen.com 有时候我们会遇到这样的场景,需要通过多线程同时去对某些信息进行处理然后再进行开展后续的业务,这时候就需要用到多线程来同时并发处理这些数据 其实多线程处理的方式有很多,线程池,自定义线程等等,但是需要同时执行,所以这里用了线程池和CountDownLatch这个类来进行处理 其实思路很简单,首先通过线程池划分好线程明细,然后通过线程池的submit进行处理,同时每一个线程处理好之后通过countDownLatch.countDown()方法将结果给汇总,最

f2315895270的博客 7930

【新华三】DHCP+TFTP+ZTP自动开局实战

设备运行ZTP功能,可以自动获取并自动加载开局文件,实现设备的免现场配置和部署,从而降低人力成本,提升部署效率。使用TFTP服务器工具进行文件的上传和下载,网上很多TFTP server软件,大家可以自行下载。3CDaemon等,可以根据使用自取所需。大家应该都了解ZTP,但是目前发现除了控制器之外仅使用ZTP开局的兄弟并不是很多,因此分享此过程方便大家批量开局,留出更多时间学习更多技术。电脑打开DHCP服务器,TFTP服务器,并通过网线连接到设备的管理口,若设备较多可以通过带外交换机等进行连接,

五大连池的镜子 4981

Java程序员之创建多线程的几种方式(三种方式、七种方法)

1.如果你是jdk1.8和之后的版本,不需要返回值的话可以考虑用lambda表达式来创建新线程2.如果需要返回值可以考虑用最后一种方法匿名callable接口。

weixin_56610646的博客 1764

微信小程序如何转云开发

小程序转云开发

zhangxiaobai___的博客 3914

Matlab处理气象数据(六)数据的面积加权

由于地理投影导致导致每个像元实际地面面积不同,越靠近北极实际面积越小,越靠近赤道实际面积越大,如果不进行面积加权就简单平均,会导致温度较实际温度偏低。 用以下公式计算地理投影每个像元的面积: double pi = 3.1415926; double R = 6371007.181; //units - m double area = (pi/180.0)*R*R*fabs(sin(dLat1/180.0*pi) - sin(dLat2/180.0*pi)) * fabs(dLon1 - dLo

设计の诗 1万+

java 多线程怎么写_Java多线程详解总结

一、基本概念程序(program): 是为完成特定任务、用某种语言编写的一组指令的集合。即指一 段静态的代码,静态对象。进程(process):是程序的一次执行过程,或是正在运行的一个程序。是一个动态 的过程:有它自身的产生、存在和消亡的过程。——生命周期运行中的QQ,运行中的MP3播放器程序是静态的,进程是动态的进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域线程(thread...

weixin_32365483的博客 965

电子电器架构刷写方案——General Flash Bootloader

### 文章大体有如下内容: ### 1、简介 ### 2、刷写系统概述 ### 3、Flash Bootloader刷写流程 ### 4、Flash Bootloader架构介绍 ### 5、关于刷写的思考

Soly_kun的博客 2211

掌握这5种多线程方法,让你的Java代码更高效

如果您需要更多的控制和灵活性,请查看 ThreadPoolExecutor,它是 Executors.newFixedThreadPool() 返回的 ExecutorService 的实际实现。通过限制特定任务使用的线程数,您可以根据优先级分配线程数:对于重要且频繁的任务使用更多线程,对于琐碎或偶尔的任务使用较少线程。虽然它在许多其他异步方法的底层使用中,但对于可以分解为较小且独立子任务的任务来说,它也非常强大,这些任务可以通过分而治之的策略来解决。请注意,b 预计计数的次数是其他实例的两倍。

资料免费分享,点击名片 575

Figma 设计稿转前端代码:Cursor 实现 UI 还原的 4 种精准映射方法

揭示 Figma 设计稿转前端代码失效的根本原因在于四层语义未对齐(像素→布局、视觉→样式、交互→行为、意图→架构),提出以语义标注为核心的结构化喂数据方法,详解通过图层命名、Description 字段和 Markdown 上下文锚点实现高精度 React 组件生成,并给出可落地的避坑指南与实测提

weixin_42545292的博客 138

代码复现1——Matterport3d数据集下载

进入“Dataset Download”部分,下载申请书,填写之后发送到指定邮箱(matterport3d@googlegroups.com),之后会收到回信,回信中附有一个download_mp.py的脚本文件。,大小约15G,并不是所有的Mp3d datasets,所有大小约1.3T。下载完成后如下图所示:我这边显示大小为16.1G。

qq_44100524的博客 8054

使用Java实现高并发的消息处理

在上述代码中,我们使用RabbitMQ作为消息队列,生产者发送消息到队列,消费者从队列中接收并处理消息,实现了高并发的消息处理。在上述代码中,我们创建了一个包含10个线程的线程池,并提交了100个消息处理任务。在上述代码中,我们创建了一个包含10个消费者线程和大小为50的阻塞队列的消息处理器,并提交了100个消息进行处理。线程池是Java中常用的并发处理工具,它可以有效地管理和重用线程,避免频繁创建和销毁线程带来的开销。在上述代码中,我们创建了一个固定大小的线程池,并提供了提交消息处理任务的方法。

省赚客开发者博客 1067

机器学习实战——疫情数据分析与预测

机器学习如何做到疫情可视化——疫情数据分析与预测实战 本文将带领大家爬取11个国家以及中国31个省(自治区、直辖市)在2022.0101-2022.06.19的新冠疫情数据。并且采用机器学习模型对2022.6.20-2022.6.30每一天的全国确诊人数、死亡人数、治愈人数进行预测,**做出疫情可视化图形并且求出最终的相关系数R2!...

拥有百余位万粉技术博主、200余位可合作代发博主,私域技术社群规模达千人,商务合作私信 10万+

Java多线程并发控制:使用ReentrantLock实现生产者-消费者模型

使用ReentrantLock实现生产者-消费者模型

言不信者行不果,行不敏者言多滞. 1641

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

多线程生产者、消费者模式中,如何停止消费者

多线程生产者、消费者模式中,如何停止消费者 ?多生产者情况下对“毒丸”策略的应用 生产者、消费者模式是多线程中的经典问题。通过中间的缓冲队列,使得生产者和消费者的速度可以相互调节。 发散:一个主线程控制多个子线程,多个主线程控制多个子线程(countDownLunch使用理解) 对于比较常见的单生产者、多消费者的情况,主要有以下两种策略: 通过volatile boolean prod...

hnhygkx的博客 3316

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

Kafka 消费者:优雅的退出消费者程序、多线程消费者以及独立消费者

1. 优雅的退出消费者程序 wakeup()方法是consumer中唯一个可以在多线程中使用的方法。poll方法不会立即响应wakeup方法,并在下次poll调用时会抛出WakeupException package com.bonc.rdpe.kafka110.consumer; import java.util.Arrays; import java.util.Properties; import org.apache.kafka.clients.consumer.ConsumerRecord

z69183787的专栏 7189

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

java:实现生产者和消费者问题(附带源码)

java:实现生产者和消费者问题(附带源码)

欢迎来到我的博客!这里是一个专注于计算机技术的分享平台,涵盖编程开发、算法研究、系统架构、软件工程等多个领域。无论你是初学者还是资深开发者,都能在这里找到有价值的内容。 1450

Java 并发编程实战:使用 BlockingQueue 优雅实现生产者消费者模式

本文介绍了使用Java的BlockingQueue实现生产者消费者模式的方法。该模式通过共享队列解耦生产者和消费者线程,解决数据同步问题。BlockingQueue提供了线程安全的put()和take()方法,自动处理队列满/空时的阻塞等待。文章详细展示了实现步骤:定义产品类、创建生产者线程、消费者线程及主程序,并分析了其线程安全、阻塞机制和解耦优势。同时给出了实际应用场景和最佳实践建议,如使用有界队列、处理中断、监控队列状态等。BlockingQueue简化了多线程编程,是构建高并发Java应用的重要工具

Cloud的专栏 1071

剖析 Java 的多线程设计模式:生产者 - 消费者模式

生产者 - 消费者模式是构建高并发系统的基石,其实现方式随 Java 版本演进不断丰富。从基础的wait()notify()到现代的Reactor响应式编程,该模式始终围绕 “解耦与平衡” 的核心目标。在实际开发中,需根据业务场景选择合适的队列类型与线程管理策略,结合性能监控工具持续优化系统。未来,随着 Loom 虚拟线程的普及,生产者 - 消费者模式的实现将更加轻量高效,为云原生应用提供更强大的并发支持。

潜意识Java的博客 1290

【Java 基础篇】Java 生产者-消费者模式详解

生产者-消费者模式是一种经典的多线程设计模式,用于解决多个线程之间的数据共享和协作问题。在生产者-消费者模式中,有两类线程:生产者线程和消费者线程。它们之间通过共享一个缓冲区(或队列)来协作,生产者将数据放入缓冲区,消费者从缓冲区取出数据并进行处理。生产者-消费者模式的主要目标是实现生产者和消费者之间的解耦,使它们可以独立地进行工作,从而提高系统的性能和可维护性。首先,我们定义一个有界缓冲区,使用来实现,它是 Java 并发包中提供的一个有界队列。} }} }} }

繁依Fanyi的博客 2555

java实现生产者和消费者问题(附带源码)

java实现生产者和消费者问题(附带源码)

欢迎来到我的博客!这里是一个专注于计算机技术的分享平台,涵盖编程开发、算法研究、系统架构、软件工程等多个领域。无论你是初学者还是资深开发者,都能在这里找到有价值的内容。 1116

生产者消费者模式全方位解析:从原理到高并发实战

生产者消费者模式是一种经典的多线程协作方案,通过缓冲区解耦生产者和消费者。生产者将数据存入缓冲区,消费者从中取出处理,两者无需直接交互。该模式具有四大优势:解耦、并发支持、平衡速度差异、支持批量处理。实现核心在于线程安全的阻塞队列,使用synchronized和wait/notify机制确保同步。这种模式广泛应用于高并发系统,如消息队列、日志处理等场景。

百锦再的博客 951

Java多线程编程实战:深入理解线程池与生产者-消费者模型

在Java开发中,多线程编程是提升程序性能的重要手段。本文将结合一个完整的项目实践,详细讲解如何使用线程池实现高效的生产者-消费者模型,并解决开发中遇到的典型问题。现象:在高并发场景下,应用突然出现内存溢出原因:未合理配置线程池队列大小,导致任务无限堆积解决方案线程安全队列:使用实现线程安全资源释放:确保消费者线程能正常退出性能监控:实时监控队列大小和线程状态异常处理:捕获并妥善处理所有可能的异常.通过这个项目实践,我们深入理解了Java多线程编程的核心概念和最佳实践。合理配置线程池参数。

LekYgn的博客 1083

Java 多线程编程实战:生产者-消费者模型解析

本文将实现一个基于阻塞队列的生产者-消费者模型,展示Java多线程编程的核心概念。System.out.println(Thread.currentThread().getName() + " 生产: " + data);System.out.println(Thread.currentThread().getName() + " 消费: " + data);通过volatile boolean isRunning标志位实现可控的线程终止,避免使用已废弃的stop()方法。

m0_54851477的博客 465

生产者消费者模式,以及基于BlockingQueue的快速实现

什么是生产者消费者模式?简单来说就是有两个角色,一个角色主要负责生产数据,一个角色主要负责消费(使用)数据。那么生产者直接依赖消费者,然后直接调用是否可以?答案是可以的,但是有些场景无法及时解决,典型的就是生产者消费者的速度无法同步,导致整体的速度上不去的情况。执行速度永远取决于二者的最小速度(假设生产者和消费者的速度时快时慢)。一般的解决方案是什么呢?我们通常会加一个中间件,作为缓冲队列。 生...

weixin_40229012的博客 269

Java:实现基于阻塞队列的“生产者消费者模型”(附带源码)

Java:实现基于阻塞队列的“生产者消费者模型”(附带源码)

欢迎来到我的博客!这里是一个专注于计算机技术的分享平台,涵盖编程开发、算法研究、系统架构、软件工程等多个领域。无论你是初学者还是资深开发者,都能在这里找到有价值的内容。 910
上一篇: 【Java多线程手写代码】(四):字节跳动面试官让我手写代码实现启动三个线程累加数字到30
下一篇: 【字节跳动面经】Java后台研发2020面试真题(商业变现部门一面)
少侠露飞
博客等级 码龄9年 214粉丝 117原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值