Java多线程案例——阻塞队列(生产者消费者模型)

news2024/10/6 14:30:45

一,阻塞队列

1.阻塞队列的概念和作用

阻塞队列同数据结构中的队列一样都遵守“先进先出”的原则(不了解队列相关知识的朋友请查看之前队列的博文:(6条消息) 栈和队列(内附模拟实现代码)_徐憨憨!的博客-CSDN博客),阻塞队列就是在原本队列的基础上加入了阻塞功能(是一种线程安全的数据结构):

  1. 当队列满的时候, 继续入队列就会阻塞, 直到有其他线程从队列中取走元素.

  1. 当队列空的时候, 继续出队列也会阻塞, 直到有其他线程往队列中插入元素.

2.阻塞队列中的相关方法

1.BlockingQueue接口

在Java标准库中内置了阻塞队列(是一个接口BlockingQueue),可直接使用,实现该接口的类有很多(其中最常用的是LinkedBlockingQueue类

ArrayBlockingQueue

一个由数组结构组成的有界阻塞队列

LinkedBlockingQueue

一个由链表结构组成的有界阻塞队列

PriorityBlockingQueue

一个支持优先级排序的无界阻塞队列

DelayQueue

一个使用优先级队列实现的无界阻塞队列

SynchronousQueue

一个不存储元素的阻塞队列

LinkedTransferQueue

一个由链表结构组成的无界阻塞队列

LinkedBlockingDeque

一个由链表结构组成的双向阻塞队列

2.put和take方法

阻塞队列也是队列包含队列的相关方法(如offerpollpeek等方法,但是这些方法不带有阻塞特性),所以在多线程下为了保证线程安全发挥其阻塞功能,一般会使用阻塞队列的puttake方法。

put:用于阻塞式的入队列,当队列满了的时候就会发生阻塞等待,直到队列出元素

take:用于阻塞式的出队列,当队列为空的时候就会发生阻塞等待,直到队列入元素

3.生产者消费者模型

阻塞队列的主要用于生产者消费者模型,用来解决生产者和消费者之间的强耦合问题

生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取(所以这里的阻塞队列主要存放的就是生产者创建的一个个任务)。

使用内置标准库来实现一个生产者消费者的示例,代码如下:

/**
 *创建两个线程:生产者线程和消费者线程
 * 生产者线程负责生产count并使得count++
 * 消费者线程负责消费count
 * 分别在生产者线程和消费者线程中打印count查看生产和消费的信息
 */
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
public class ThreadDemo1 {
    public static void main(String[] args) {
        BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<>();
        //消费者线程读取阻塞队列中的元素
        Thread customer = new Thread(() -> {
            while (true) {
                try {
                    int ret = blockingQueue.take();
                    System.out.println("消费:" + ret);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        customer.start();

        //生产者线程输入元素到阻塞队列中
        Thread producer = new Thread(() -> {
            int count = 0;
            while (true) {
                try {
                    System.out.println("生产:" + count);
                    blockingQueue.put(count);
                    Thread.sleep(500);
                    count++;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        producer.start();
    }
}

通过结果可以看出,这里的生产和消费都是成对出现,一般是生产者线程一个任务消费者线程就会消费一个线程(主要原因是因为我们在生产者线程中使用了sleep方法控制了生产者的生产速率,从而达到消费者线程消费完一个任务之后生产者才会继续生产),如果我们取消sleep操作会发生什么?

通过看出结果是不受控制的,因为两个线程是并发执行的,生产者线程一瞬间会生产很多任务,消费者线程一瞬间也会消费很多任务,所以对生产者和消费者合理加入sleep操作可以更好的控制消费和生产之间的效率

生产者消费者模型的好处

  1. 实现了消费者和生产者之间的“解耦”(即降低可耦合过程)

假设有两个服务器A,B:A向B发送请求,B执行完A的请求之后再响应给A;

假如某一时刻A发送的请求激增,B来不及响应势必会造成B的崩溃(此时AB之间的耦合性非常高),于是提出了阻塞队列的想法,在AB之间加一个阻塞队列,A将请求全部放在阻塞队列中,不需要直接将请求发送给B,B直接从阻塞队列中接受请求,不需要直接响应A(此时AB之间的耦合性不高)

  1. 可以做到“削峰填谷”,保证系统的稳定性

接着上面AB服务器的例子,如果某一阶段A发送的请求增多,B来不及响应,此时B服务器就会崩溃,但是加入阻塞队列之后,不论发送的请求有多少都会放在阻塞队列中,队列满了就会阻塞等待,等待B从阻塞队列中执行响应,从而保证了系统的稳定性。

二,模拟实现阻塞队列

使用循环队列模拟实现阻塞队列

/**
 * 自己模拟实现阻塞队列,不考虑泛型
 * 使用循环队列模拟实现阻塞队列
 * 定义一个size说明此时队列中的元素个数
 */
class MyBlockingQueue {
    private int[] elem = new int[1000];
    //显示初始化
    private int head = 0;
    private int tail = 0;
    private int size = 0;

    //入队
    public void put(int val) throws InterruptedException {
        synchronized (this) {
            while (size == elem.length) {
                //队列满了,不能继续插入
                //此时要产生阻塞
                //return;
                this.wait();
            }
            elem[tail] = val;
            tail++;
            //记得对tail的处理(因为这里是环形队列)这种方式可读性更高,而且效率也更高 因为去摸操作开销更大
            //计算机算加减操作快,算乘除操作慢
            if (tail == elem.length) {
                tail = 0;
            }
            //tail = tail % elem.length;//写成这种方式是一样的
            size++;

            //唤醒take中的wait
            this.notify();
        }
    }

    //出队列
    public Integer take() throws InterruptedException {
        Integer ret;
        synchronized (this) {
            while (size == 0) {
                //队列为空,出队失败
                //return null;
                this.wait();
            }
            ret = elem[head];
            head++;
            if (head == elem.length) {
                head = 0;
            }
            size--;

            //唤醒put中的wait
            this.notify();
        }
        return ret;
    }
}
  1. 分别在put和take方法中加入wait和notify方法,put方法中的wait的作用是当队列满了需要阻塞等待,等待take方法中取出元素;take方法中的wait的作用是当队列为空时需要阻塞等待,等待put方法中入元素;

  1. 两个方法中的wait方法并不会同时出发,因为队列不可能同时为满和为空;

  1. 方法中涉及到写的操作,会造成线程不安全的问题,所以需要分别加上synchronized关键字进行加锁。

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

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

相关文章

功率放大模块如何选择(安泰功率放大器模块产品介绍)

功率放大器模块系列产品介绍 一、功率放大模块介绍 功率放大模块&#xff1a; 功率放大模块具有体积小&#xff0c;集成度高&#xff0c;使用方便&#xff0c;应用广泛等优点&#xff0c;凭借着输出频率广、输出电压高、输出功率大等特性&#xff0c;能够广泛应用在各种领域…

动态范围控制原理

DRC介绍 开门见山&#xff0c;动态范围的定义就是信号的最大幅值和最小幅值比值的对数(单位dB)&#xff0c; 动态范围会受到系统中各个环节的影响。例如同样是这段音乐&#xff0c;在一个40dB背景噪声的环境中播放&#xff0c;那么由于掩蔽效应等因素的影响&#xff0c;最终实际…

前端跳转第三方网页中间页

前端跳转安全提示 掘金跳转中间页背景介绍跳转过渡页的优点实现原理解析哈喽啊小伙伴们久等了 消失了有半年了 &#xff0c;因为个人工作原因没腾出时间给大家分享日常踩坑和特殊功能的讲解。不过这次我回来了就要好好分享了背景介绍 前端小伙伴一定知道CSDN 和 稀土掘金 两大…

Dev-C++下载安装详细教程

文章目录前言一、下载Dev-C二、安装Dev-C三、使用Dev-C打印HelloWorld总结前言 本文总结了关于Dev-C下载与安装的详细过程&#xff0c;并使用Dev-C打印了“Hello World!”。本篇博客面向C语言初学者&#xff0c;或者考研复试的学生使用&#xff0c;因为大部分学校的考研复试都使…

为什么 TCP 建立连接需要三次握手

TCP 协议是我们几乎每天都会接触到的网络协议&#xff0c;绝大多数网络连接的建立都是基于 TCP 协议的&#xff0c;学过计算机网络或者对 TCP 协议稍有了解的人都知道 —— 使用 TCP 协议建立连接需要经过三次握手&#xff08;three-way handshake&#xff09;。 如果让我们简…

多线程案例-线程池

1.什么是线程池线程存在的意义是当使用进程进行并发编程太重了,此时引入了一个"轻量级的"进程-线程.创建线程比创建进程更高效,销毁线程比销毁进程更高效,调度线程比调度进程更高效..此时我们就用多线程来代替进程进行并发编程了,但是随着对性能的要求的提高,线程相对…

大数据必学Java基础(一百一十八):什么是Maven和它的下载整合

文章目录 什么是Maven和它的下载整合 一、什么是Maven 二、IDEA默认整合了Maven 三、下载地址

【Linux】RHEL8 中nmcli使用,必备!

redhat8中nmcli日常使用 第 2 章 配置以太网连接 Red Hat Enterprise Linux 为管理员提供不同的选项来配置以太网连接。例如&#xff1a; 在命令行中使用 nmcli 配置连接。使用 nmtui 在基于文本的用户界面中配置连接。使用 RHEL 系统角色在一个或多个主机上自动配置连接。使…

电脑小问题:定时关机的设置

设置定时关机生活中&#xff0c;我们有时候需要对电脑进行定时关机。那么&#xff0c;如何设置定时关机呢&#xff1f;步骤如下&#xff1a; 1. 按 win R &#xff0c;弹出命令窗口&#xff0c;输入 taskschd.msc &#xff0c;点击确定。 2. 弹出任务计划程序窗口&#xff0c;…

使用ResNet34实现CIFAR10数据集的训练

如果对你有用的话&#xff0c;希望能够点赞支持一下&#xff0c;这样我就能有更多的动力更新更多的学习笔记了。&#x1f604;&#x1f604; 使用ResNet进行CIFAR-10数据集进行测试&#xff0c;这里使用的是将CIFAR-10数据集的分辨率扩大到32X32&#xff0c;因为算力相关的…

摘要/哈希/散列算法MD5 SHA1 SHA256 SHA512的区别和MAC算法

一、摘要算法大致都要经过以下步骤 1. 明文数据预处理 1.1 填充比特 MD5、SHA1、SHA256 的分组长度都是512bit 需要填充比特使其位长对512求余的结果等于448 SHA512 的分组长度是 1024bit 需要填充比特使其对1024求余的结果等于896 相同&am…

ECharts基本使用

文章目录Echarts概述Echarts初体验ECharts基础配置Echarts社区介绍Echarts-map使用Echarts概述 常见的数据可视化库&#xff1a; D3.js 目前 Web 端评价最高的 Javascript 可视化工具库(入手难)ECharts.js 百度出品的一个开源 Javascript 数据可视化库Highcharts.js 国外的前…

项目合并后,font字体资源被替换导致TextMeshPro不能显示文字,抢救方法

一&#xff0c;字体消失 项目合并时&#xff0c;因为资源更替&#xff0c;导致TextMeshPro不能找到自己原来使用的font资源&#xff0c;以致不能显示文字。 二、抢救方式 1、找到所有用到TextMeshPro的物体2、把他们的字体重新设置成你要的字体 关键步骤&#xff1a; 1、找…

赛事推荐| 建筑物实例分割和高度估计的多任务学习——2023 IEEE GRSS 数据融合赛道2

1. 赛题名称 联合建筑物提取和高度估计的多任务学习 2. 赛题背景 该轨道定义了建筑物提取和高度估计的联合任务。两者都是建筑改造的两个非常基础和必不可少的任务。与轨道 1 相同&#xff0c;输入数据是多模态光学和 SAR 卫星图像。单视图卫星图像中的建筑物提取和高度估计…

记录redis连接被打满的踩坑之路

一、系统异常现象系统有一个功能向别的系统多线程推送用户数据信息&#xff0c;前几天发现该推送功能报内部错误&#xff0c;经过查看后台日志文件&#xff0c;发现org.redisson.client.RedisConnectionException: Unable to connect to Redis server:&#xff0c;io.netty.cha…

使用docker训练yolov5

使用docker训练yolov5 配置docker&#xff0c;配置的好处是docker中的环境或者说容器坏了不影响主机&#xff0c;并且可以减少配置环境的时间和精力 sudo apt update sudo apt install apt-transport-https ca-certificates curl gnupg-agent software-properties-common # c…

Docker 部署SQL Server 2017

Docker 部署SQL Server 2017 Docker部署 registry Docker搭建 svn Docker部署 Harbor Docker 部署SQL Server 2017 Docker 安装 MS SqlServer Docker部署 Oracle12c 文章目录Docker 部署SQL Server 2017一、部署步骤1.下载镜像2.创建容器并运行二、参考文档一、部署步骤 1.下…

Unity 之 资源加载 -- 可寻址系统概念介绍 -- 入门(一)

可寻址系统面板概念 -- 入门&#xff08;一&#xff09;一&#xff0c;可寻址系统概念介绍1.1 官方话术1.2 几个概念二&#xff0c;可寻址系统目录介绍2.1 导入工程2.2 目录介绍概述&#xff1a;本片文章带大家了解可寻址系统的相关概念&#xff0c;为大家介绍可寻址系统导入方…

生成数据分析报告pandas_profiling.ProfileReport

【小白从小学Python、C、Java】 【计算机等级考试500强双证书】 【Python-数据分析】 生成数据分析报告 pandas_profiling.ProfileReport 选择题 对于以下python代码表述错误的一项是? import pandas as pd import pandas_profiling as pp dfpd.DataFrame({ a:[23,18,21], b:[…

excel数据核对技巧:如何用函数公式标识输入正误

我们平时人工录入较长的文本数据时&#xff0c;稍不注意就容易出错。为了避免出错&#xff0c;通常我们会提前对单元格设置数据验证。有些时候&#xff0c;我们还会考虑列与列之间的关系&#xff0c;根据列关系自动判定数据的对错。比如下表,款号、货号、色号、条码的信息均存在…