【Java系列】多线程案例学习——基于阻塞队列实现生产者消费者模型

个人主页:兜里有颗棉花糖
欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 兜里有颗棉花糖 原创
收录于专栏【Java系列专栏】【JaveEE学习专栏】
本专栏旨在分享学习JavaEE的一点学习心得,欢迎大家在评论区交流讨论💌
在这里插入图片描述

目录

  • 一、阻塞式队列
  • 二、生产者消费者模型
    • 生产消费者模型的优势
  • 三、生产者消费者举例代码(基于阻塞队列)
  • 四、基于阻塞式队列实现生产者消费者模型

一、阻塞式队列

什么是阻塞式队列(有两点):

  • 第一点:当队列满的时候,如果此时入队列的话就会出现阻塞,直到其它线程从队列中取走元素为止。
  • 第二点:当队列为空的时候,如果继续出队列,此时就会出现阻塞,一直阻塞到其它线程往队列中添加元素为止。

二、生产者消费者模型

什么是生产者消费者模型:
生产者消费者模型是常见的多线程编程模型,可以用来解决生产者和消费者之间的数据交互问题。

阻塞队列的最主要的一个目的之一就是实现生产者消费者模型(基于阻塞队列实现),生产者消费主模型是处理多线程问题的一种方式。

生产消费者模型的优势

生产者消费主模型的优势:针对分布式系统有两个优势,一个是解耦合(耦合我们可以理解为依赖程度)、另一个是削峰填谷

  • 解耦合:生产者和消费主之间通过缓冲区进行解耦合,而不会对彼此产生直接的依赖,我们通过引入生产者消费者模型(即阻塞队列)就可以达到解耦合的效果,但是付出的代价就是效率有所降低。

  • 削峰填谷:服务器接收到的来自用户端的请求数量可能会因为一些突发时间而暴增,此时服务器面临的压力就非常大了。我们要知道一台服务器承担的上限是一样的,不同的服务器所能承担的上限又是不同的。(机器的硬件资源(CPU、内存、硬盘、网络带宽等等)是有限的,而服务器每处理一个请求都需要消耗一定的资源,请求足够多直到机器的硬件资源招架不住的时候服务器也就挂了)通过引入生产消费者模型(即阻塞队列)就可以起到一个缓冲的作用,其中阻塞队列就承担了服务器的一部分压力,然后当峰值消退的时候,服务器接收到的请求就相对较少了,此时服务器由于阻塞队列的原因依然可以按照既定的顺序处理请求。

  • ‘’

阻塞队列只是一个数据结构,如果我们把这个数据结构单独实现称了一个服务器程序,并且使用单独的主机或者主机群来进行部署的话,此时阻塞式队列就进化成了消息队列。而在Java标准库中已经实现了阻塞队列,并且实现了三种阻塞队列的实现方式:

三、生产者消费者举例代码(基于阻塞队列)

生产消费者模型代码如下(基于阻塞式队列):

import java.util.concurrent.BlockingQueue;import java.util.concurrent.LinkedBlockingQueue;// 生产消费者模型——阻塞队列
public class Demo20 {public static void main(String[] args) {// 创建一个阻塞队列来作为交易场所BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(10);Thread t1 = new Thread(() -> {int count = 0;while(true) {try {queue.put(count);System.out.println("生产元素:" + count);count++;Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});Thread t2 = new Thread(() -> {while(true) {while(true) {try {Integer n = queue.take();System.out.println("消费元素:" + n);} catch (InterruptedException e) {e.printStackTrace();}}}});t1.start();t2.start();}
}

代码运行结果如下:
在这里插入图片描述

四、基于阻塞式队列实现生产者消费者模型

现在,我们自己来基于循环队列来实现阻塞式队列。注意我们这里实现的阻塞队列是基于数组、基于循环队列的阻塞队列。

我们在实现阻塞队列的时候有以下几点需要注意:

  • 线程安全问题:需要给put方法take()方法进行加锁操作。
  • 经过加锁之后还需要考虑到内存可见性问题,这里就涉及到volatile关键字的使用。
  • 阻塞状态以及阻塞状态的解除时机要把握好(即wait()方法notify()方法的使用)。
  • wait()方法不一定是被notify()方法唤醒的,还有可能是被interrupt()方法唤醒的:如果interrupt方法是按照try catch的形式来进行编写的,一旦interrupt方法唤醒wait方法,接着执行完catch之后,代码并不会结束而是继续往后执行,此时就会出现覆盖元素的问题。(解决方法,使用while循环不断等待和检查条件。如果不使用 while 循环在状态被满足之前不断地等待和检查条件,就有可能在 wait 方法返回之后仍然不能安全地进行操作,这可能导致程序出现异常和错误。强烈建议使用wait方法的时候搭配while循环来判定条件

代码如下:

class MyBlockQueue {// 使用string类型的数组来保存元素,我们假设这里只存stringprivate String[] items = new String[1000];//head表示指向队列的头部volatile private int head = 0;volatile private int tail = 0;volatile private int size = 0; // size表示元素个数private Object locker = new Object();public void put(String elem) throws InterruptedException {synchronized(locker) {while(size >= items.length) {//队列已满locker.wait();//return;}items[tail] = elem;tail++;if(tail >= items.length) {tail = 0;}//tail++和下面的if判断可以替换成tail = (tail + 1) % (items.length)//但是站在CPU的角度来看,其实还是简单的if判断比较快size++;locker.notify(); // 用来唤醒队列为空的阻塞情况}}//出队列public String take() throws InterruptedException {synchronized(locker) {while(size == 0) {locker.wait();}String elem = items[head];head++;if(head >= items.length) {head = 0;}size--;//使用notify来唤醒队列阻塞满的情况locker.notify();return elem;}}
}public class Demo21 {public static void main(String[] args) {// 创建两个线程分别表示消费者和生产者MyBlockQueue queue = new MyBlockQueue();Thread t1 = new Thread(() -> {int count = 0;while(true) {try {queue.put(count + "");System.out.println("生产元素: " + count);count++;} catch (InterruptedException e) {e.printStackTrace();}}});Thread t2 = new Thread(() -> {while(true) {try {String count = queue.take();System.out.println("消费元素: " + count);Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});t1.start();t2.start();}
}

本文到这里就结束了,希望友友们可以支持一下一键三连哈。嗯,就到这里吧,再见啦!!!

在这里插入图片描述

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

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

相关文章

如何在Mac中设置三指拖移,这里有详细步骤

三指拖移手势允许你选择文本&#xff0c;或通过在触控板上用三指拖动窗口或任何其他元素来移动它。它可以用于快速移动或调整窗口、文件或图像在屏幕上的位置。 然而&#xff0c;这个手势在默认情况下是禁用的&#xff0c;因此在本教程中&#xff0c;我们将向你展示如何在你的…

【C++】字符串常量 与 字符数组 的区别

字符串常量&#xff1a;"abc" 字符数组&#xff1a;char a[3]{a,b,c}; 那么它们相等吗&#xff1f;它们之间的区别是什么呢&#xff1f; 答&#xff1a;不相等 区别在于 字符串常量"abc" 的本质是以空字符\0结尾的字符数组&#xff0c; 而char a[3]{a,b,…

数据库系统原理例题之——SQL 与关系数据库基本操作

SQL 与关系数据库基本操作 第四章 SQL 与关系数据库基本操作【例题】一 、单选题二 、填空题三 、简答题四 、设计题 【答案&解析】一、单选题二、填空题三、简答题四、设计题 【延伸知识点】【延伸知识点答案&解析】 第四章 SQL 与关系数据库基本操作 【例题】 一 、…

XML与Java解析

XML规范统一&#xff0c;与操作系统、编程语言的开发平台无关。 在存储数据、交换数据、数据配置方面有优势。 格式如下 <?xml version"1.0" encoding"UTF-8"?> <books><!--图书信息 --><book id"101"><author&…

springboot整合minio做文件存储

一,minio介绍 MinIO 是一个基于Apache License v2.0开源协议的对象存储服务。它兼容亚马逊S3云存储服务接口&#xff0c;非常适合于存储大容量非结构化的数据&#xff0c;例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等&#xff0c;而一个对象文件可以是任意大小&…

TimeoutException(超时异常)可能的原因和解决方法

TimeoutException 通常表示一个操作在规定的时间内没有完成。以下是可能导致 TimeoutException 的一些常见原因以及相应的解决方法&#xff1a; 网络连接超时&#xff1a; 可能原因&#xff1a; 尝试与远程主机建立网络连接时&#xff0c;连接超过了指定的时间。解决方法&#…

verilog rs232串口模块

前面发了个发送模块&#xff0c;这次补齐&#xff0c;完整。 串口计数器&#xff0c;波特率适配 uart_clk.v module uart_clk(input wire clk,input wire rst_n,input wire tx_clk_en,input wire rx_clk_en,input wire[1:0] baud_sel,output wire tx_clk,output wire rx_clk )…

Kubernetes快速实战与核心原理剖析

K8S 概览 K8S 是什么 K8S 官网文档&#xff1a;https://kubernetes.io/zh/docs/home/ K8S 是 Kubernetes 的全称&#xff0c;源于希腊语&#xff0c;意为“舵手”或“飞行员”。Kubernetes 是用于自动部署、扩缩和管理容器化应用程序的开源系统。 Kubernetes 源自 Google 15 年…

知识笔记(六十三)———JavaScript 工具库 | PrefixFree给CSS自动添加浏览器前缀

为了解决这个问题&#xff0c;国外的牛人开发了了一个 -Prefix-free 的插件&#xff0c;能够自动给我们添加这些前缀&#xff0c;我们仅仅需要编写一次代码&#xff0c;无需在考虑是否兼容其他浏览器&#xff0c;而且如果后面浏览器支持这个属性了&#xff0c;我们只需要移除 -…

EDKII:第一个Helloworld

目录 0 说明 1 步骤 1.1 简介 1.2 创建新文件 1.3 创建printhelloworld.c、printhelloworld.inf&#xff1a; 1.4 修改MdeModulePkg\MdeModulePkg.dsc 1.5 修改EmulatorPkg\EmulatorPkg.dsc 1.6 运行 0 说明 上篇文章记录了如何安装UEFI环境&#xff0c;在这里将会写下…

c++ / day03

1. 定义一个Person类&#xff0c;包含私有成员&#xff0c;int *age&#xff0c;string &name&#xff0c;一个Stu类&#xff0c;包含私有成员double *score&#xff0c;Person p1&#xff0c;写出Person类和Stu类的特殊成员函数&#xff0c;并写一个Stu的show函数&#xf…

CodeWhisperer——轻松使用一个超级强大的工具

CodeWhisperer 简介 CodeWhisperer是亚⻢逊云科技出品的一款基于机器学习的通用代码生成器&#xff0c;可实时提供代码建议。 CodeWhisperer有以下几个主要用途&#xff1a; 解决编程问题&#xff0c;提供代码建议&#xff0c;学习编程知识等等&#xff0c;并且CodeWhisper…

基于人工势场法的航线规划

MATLAB2016b可以运行 基于人工势场法的航线规划资源-CSDN文库

JavaSE学习笔记 2023-12-21 --流

十九、流 上一篇 个人整理非商业用途&#xff0c;欢迎探讨与指正&#xff01;&#xff01; 文章目录 十九、流19.1流的概念19.2File类19.2.1File对象的创建19.2.2Java中的路径表示19.2.3File中的常用方法19.2.4FileNameFilter接口 19.3IO流19.3.1流的划分19.3.2字节流[重点]…

常用的 linux 命令

常用的 linux 命令 1.从其他机器拷贝文件夹2.查看哪个程序在用特定端口3.实时监控日志文件内容4.查看指定用户拥有的进程5.查看磁盘空间使用情况6.文件搜索which&#xff08;whereis&#xff09; 显示系统命令所在目录find 查找任何文件或目录1&#xff09; 根据文件名称查找2)…

具身智能主流方法:模仿学习,和强化学习

1.区别 模仿学习&#xff1a;倾向于从优秀的个体展现出来的技能中快速学习&#xff0c;并获得泛化能力&#xff0c;但模仿学习目前学到的仅是相同技能的不用应用&#xff0c;比方说&#xff0c;“放苹果”泛化到“放梨”&#xff0c;“放牛奶”&#xff0c;都是“放”这个技能的…

磁盘——磁盘管理与文件系统

目录 一、在linux中使用硬盘分三步 1、分区 2、文件系统&#xff08;管理大小权限。日志恢复&#xff09; 3、挂载&#xff08;硬盘和系统文件做关联&#xff0c;使用文件夹使用系统&#xff09; 二、磁盘结构 三、MBR与GPT磁盘分区 1、分区的原因&#xff0c;为什么分区…

Ubuntu18.04安装GTSAM库并验证GTSAM是否安装成功(亲测可用)

在SLAM&#xff08;Simultaneous Localization and Mapping&#xff09;和SFM&#xff08;Structure from Motion&#xff09;这些复杂的估计问题中&#xff0c;因子图算法以其高效和灵活性而脱颖而出&#xff0c;成为图模型领域的核心技术。GTSAM&#xff08;Georgia Tech Smo…

Java八股文面试全套真题【含答案】- RocketMQ篇

以下是关于Java八股文面试全套真题- RocketMQ篇 1.RocketMQ 是什么&#xff1f;它的特点和优势是什么&#xff1f; RocketMQ 是一个开源的分布式消息中间件系统&#xff0c;具有高吞吐量、低延迟、可靠性强等特点。 特点和优势&#xff1a; 高吞吐量&#xff1a;支持每秒百万级…

Mybatis 动态 SQL - foreach

动态SQL的另一个常见需求是需要迭代一个集合&#xff0c;通常用于构建IN条件。例如&#xff1a; <select id"selectPostIn" resultType"domain.blog.Post">SELECT *FROM POST P<where><foreach item"item" index"index&quo…