多线程案例-阻塞队列

news2024/12/22 16:37:28

阻塞队列是什么

阻塞队列是一种特殊的队列.也遵循"先进先出"的原则

阻塞队列能是一种线程安全的数据结构,并且具有以下特性:

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

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

阻塞队列的一个典型应用场景就是"生产者消费者模型".这时一种非常典型的开发模型.

生产者消费者模型

实际开发中,经常会涉及到分布式系统.服务器整个功能不是由一个服务器全部完成的.而是每个服务器负责一部分功能.通过服务器间的网络通信,最终完成整个功能.

生产者消费者模型就是通过一个容器来解决生产者和消费者的强耦合问题.(更好地做到解耦合的能力).

生产者和消费者彼此之间不再进行通讯,而是通过阻塞队列来进行通讯,所以生产者生产完数据不用再等消费者处理,而是直接丢给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列中取.

示意图如下:

1.阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力.(削峰填谷)

比如在"秒杀"的场景下,服务器同一时刻可能会受到大量的支付请求.如果直接处理这些支付要求,服务器可能扛不住(每个支付请求的处理都需要比较复杂的流程,即使一个请求消耗的资源少,但加到一起,总的消耗的资源就多了,任何一种硬件资源达到瓶颈,服务器都会挂).这个时候就可以把这些请求都放到一个阻塞队列中,然后再由消费者线程慢慢来处理每个支付请求. 

这样做可以有效做到"削峰",防止服务器被突然来到的一波请求直接冲垮(挂的直观现象:给它发请求,无回应).

2.阻塞队列也能使生产者和消费者之间"解耦"

比如过年一家人一起包饺子.一般都是有明确分工,比如一个人负责擀饺子皮,其他人负责包.擀饺子皮的人就是"生产者",包饺子的人就是"消费者".

擀饺子皮的人并不关心包饺子的人是谁(能包就行,无论是手工,借助工具还是机器),包饺子的人也不需要关心擀饺子皮的人是谁(有饺子皮就行,无论是用擀面杖擀的,还是用ipadAir5擀的)

补充说明:

(1)上述描述的阻塞队列,并非是简单的数据结构,而是基于这个这个数据结构实现的服务器程序,又被部署到单独的主机上了(消息队列)

(2)整个系统的结构更复杂了.你要维护的服务器更多了

(3)效率.引入中间商,还是有差价的.比如在上面的图当中,请求从A出来到B收到.过程中的就经历队列的转发,这个过程有一定开销.

标准库中的阻塞队列

在Java标准库中内置了阻塞队列.如果我们需要在一些程序中使用阻塞队列,直接使用标准库中的即可.

譬如有:ArrayBlockingQueue, LinkedBlockingQueue,PriorityBlockingQueue.但最常用的是

LinkedBlockingQueue.

BlockingQueue是一个接口.真正实现的类是LinkedBlockingQueue.

put方法用于阻塞式的入队列,take用于阻塞式的出队列.

BlockingQueue也有offer,poll,peek等方法,但是这些方法不具有阻塞特性.

简单的代码示例:

public class BlockingQueueTest {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<String> queue = new LinkedBlockingQueue<>();
        //入队列
        queue.put("abc");
        //出队列.如果没有put直接take,会阻塞.
        String elem = queue.take();
        System.out.println(elem);
    }
}

生产者消费者模型

实际开发中,生产者消费者模型,往往是多个生产者多个消费者.

这里的生产者和消费者往往不仅是一个线程,也可能是独立的服务器程序.甚至是一组服务器程序. 

代码示例如下:

public class TestCustomerAndProducer {
    public static void main(String[] args) {
        BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<Integer>();

        Thread customer = new Thread(() -> {
           while(true) {
               try {
                   int value = blockingQueue.take();
                   System.out.println("消费元素: " + value);
                   Thread.sleep(500);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
        }, "消费者");
        
        Thread producer = new Thread(() -> {
            Random r = new Random();
            while(true) {
                try {
                    int num = r.nextInt(1000);
                    System.out.println("生产元素: " + num);
                    blockingQueue.put(num);
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "生产者");

        customer.start();
        producer.start();
    }
}

 阻塞队列的实现

通过"循环队列"的方式实现.

使用synchronized进行加锁控制.

put插入元素的时候,判定如果队列满了,就进行wait.(注意,要在循环中进行wait.被唤醒时不一定队列就不满了,因为同时可能是唤醒了多个线程).

take取出元素的时候如果判定队列为空,就进行wait(也是循环wait).

下面展示代码(注意注释中的重点):

public class MyBlockingQueue {
    //主题内容指定为一个含有1000个元素的数组
    public int[] elems = new int[1000];
    private volatile int size = 0;
    private volatile int head = 0;
    private volatile int tail = 0;
    //锁对象
    private Object locker = new Object();

    public synchronized int getSize() {
        return size;
    }

    public void put(int value) throws InterruptedException {
        //锁加到这里和加到方法上的本质是一样的,加到方法上是给this加锁,此处是给locker对象加锁.
        synchronized (locker) {
            while(size >= elems.length) {//1
                //队列满了
                //后续需要让这个代码能够阻塞
                locker.wait();
            }
            //新的元素要放到tail指向的位置上
            elems[tail] = value;
            tail = (tail + 1) % elems.length;
            size++;
            //入队之后唤醒(可能有阻塞的take方法)
            locker.notify();
        }
    }

    public int take() throws InterruptedException {
        int ret = 0;
        synchronized (locker) {
            while(size <= 0) {//1
                //队列空了
                //后续也需要让这个代码阻塞
                locker.wait();
            }
            //取出head位置的元素并返回
            ret = elems[head];
            head = (head + 1) % elems.length;
            size--;
            //元素出队列成功后,加上唤醒
            locker.notify();
        }
        return ret;
    }
}

我相信大家应该能了解锁是怎么加的,这里不过多赘述.

那可能就会有人问,1处的判断处为什么用的是while,而不是if?

 这主要是因为put和take中使用的是同一把锁.我们可能会想到,如put中元素满了阻塞,然后take出元素了,这里就解锁.但我要说的是,如果是put成功了(这里put之后队列刚好满了),又唤醒了另一个阻塞的put,又进行put,显然会出错,那么就可以加上循环条件,如果队列一直是满的,就不再被唤醒,所以用while.

 

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

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

相关文章

小电流MOSFET 选型分析数据,可应用于电子烟,电动工具,智能穿戴等产品上

小电流双N&#xff0c;D-N通道MOSFET&#xff0c;电压60V-100V左右 电流300mA-500MA&#xff0c;采用封装形式多样。具有低导通电阻&#xff0c;可快速切换速度&#xff0c;易于设计的驱动电路也易于并联&#xff0c;ESD保护&#xff0c;低电压驱动使该器件非常适合便携式设备…

相控与斩控交交调压(THD的计算)

相控与斩控交交调压&#xff08;THD的计算&#xff09;

ML81N服务条目表创建审批

ML81N服务条目表创建 ME23N创建服务类采购订单 ML81N根据采购订单明细创建服务条目表 保存后采购订单会生成物料凭证 删除 创建 参考ESLL表&#xff0c;需要区分父包&#xff0c;子包&#xff1b;其中ls_esll-pln_pckg需要根据EKPO关联ESSR关联ESLL获取到原始包装编号 …

WampServer本地部署结合内网穿透实现公网访问本地服务

文章目录 前言1.WampServer下载安装2.WampServer启动3.安装cpolar内网穿透3.1 注册账号3.2 下载cpolar客户端3.3 登录cpolar web ui管理界面3.4 创建公网地址 4.固定公网地址访问 前言 Wamp 是一个 Windows系统下的 Apache PHP Mysql 集成安装环境&#xff0c;是一组常用来…

认识lambda架构(架构师考试复习)

Lambda架构主要分为三层&#xff0c;批处理层、加速层和服务层。 如下图所示&#xff1a; &#xff08;1&#xff09;批处理层&#xff08;Batch Layer&#xff09;&#xff1a;存储数据集&#xff0c;在数据集上预先计算查询函数&#xff0c;并构建查询对应的view。Batch Lay…

perl处理json的序列化和反序列化

perl可以使用JSON模块很方便的处理json的序列化和反序列化。先来一段简单的例子&#xff1a; #! /usr/bin/perl use v5.14; use JSON; use IO::File;my $info {id > 1024,desc > hello world,arry > [1, 2, 3, 4, 5],obj > {char > [ A, B, C ]} };say to_jso…

企业网站运营不稳定有什么影响

如果一个公司的网站打开都有困难&#xff0c;那么用户会对这个企业的实力产生怀疑&#xff0c;企业网站除了作为企业的名片外&#xff0c;更多的是承担增加企业交易订单的任务。因此很多网站会做有关的网络广告或者搜索引擎优化的工作。如果网站无法正常打开&#xff0c;那么用…

从零开发短视频电商 AWS OpenSearch Service开发环境申请以及Java客户端介绍

文章目录 创建域1.创建域2.输入配置部署选项数据节点网络精细访问控制访问策略 获取域端点数据如何插入到OpenSearch ServiceJava连接OpenSearch Servicespring-data-opensearchelasticsearch-rest-high-level-clientopensearch-rest-clientopensearch-java 因为是开发测试使用…

视频中自监督学习:「我的世界」下指令理解与跟随

本文介绍了北京大学人工智能研究院梁一韬助理教授所带领的 CraftJarvis 团队在「我的世界」环境下探索通用智能体设计的新进展&#xff0c;题为“GROOT: Learning to Follow Instructions by Watching Gameplay Videos”。 ​ GROOT 该研究的核心目标是探索能否摆脱文本数据的标…

12.11 作业

1&#xff0c; 完善对话框&#xff0c;点击登录对话框&#xff0c;如果账号和密码匹配&#xff0c;则弹出信息对话框&#xff0c;给出提示”登录成功“&#xff0c;提供一个Ok按钮&#xff0c;用户点击Ok后&#xff0c;关闭登录界面&#xff0c;跳转到其他界面 如果账号和密码…

MySQL笔记-第01章_数据库概述

视频链接&#xff1a;【MySQL数据库入门到大牛&#xff0c;mysql安装到优化&#xff0c;百科全书级&#xff0c;全网天花板】 文章目录 第01章_数据库概述1. 为什么要使用数据库2. 数据库与数据库管理系统2.1 数据库的相关概念2.2 数据库与数据库管理系统的关系2.3 常见的数据库…

关于王道3.4_3GBN协议中对滑动窗口大小范围的讲述模糊表述的思考

这里是假设滑动窗口大小为4的不合理环境下&#xff0c;对为什么不合理原因的模糊表述。 下面是我对理解&#xff1a;为什么不可以是4 发送方在收到第一个ack0之后滑动窗口后移&#xff0c;可发送窗口内为1230&#xff0c; 接收方收到ack0此时存在二义性&#xff1a; 1、发送方发…

DelteE2000计算,C代码实现

CIEDE2000色差公式主要对CIE94公式做了如下几项修正&#xff1a; 重新标定近中性区域的a*轴&#xff0c;以改善中性色的预测性能&#xff1b; 将CIE94公式中的明度权重函数修改为近似V形函数&#xff1b; 在色相权重函数中考虑了色相角&#xff0c;以体现色相容限随颜色的色相…

springboot 极简案例

安装idea File -> New Project 选择依赖 创建controller文件 输入controller类名 输入代码 运行项目 访问 localhost:8080/hello/boot package com.example.demo;import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.…

让你从此不再惧怕ANR

原文链接 让你从此不再惧怕ANR 这篇文章是基于官方的Diagnose and fix ANRs翻译而来&#xff0c;但也不是严格的翻译&#xff0c;原文的内容都在&#xff0c;又加上了自己的理解以及自己的经验&#xff0c;以译注的形式对原文的作一些补充。 当一个Android应用的UI线程被阻塞时…

【Jeecg Boot 3 - 第二天】1.1、后端 docker-compose 部署 JEECGBOOT3

一、场景 二、实战 ▶ 2.1 修改配置文件 &#xff1e; 目的一&#xff1a;将 dev 变更为生产环境 prod &#xff1e; 目的二&#xff1a;方便spring项目调用docker同个network下的redis和mysql ▶ 2.2 编写dockerfile ▶ 2.3 编写docker-compose.yaml ▶ 2.4 打…

老师们居然这样把考试成绩发给家长

教育是一个复杂而多元的过程&#xff0c;其中考试成绩的发布和沟通是教育过程中的一个重要环节。然而&#xff0c;有些老师在发布考试成绩时&#xff0c;采取了一些不恰当的方式&#xff0c;给家长和学生带来了不必要的困扰和压力。本文将探讨老师们不应该采取的发布考试成绩的…

Docker | 自定义网络

✅作者简介:大家好,我是Leo,热爱Java后端开发者,一个想要与大家共同进步的男人😉😉 🍎个人主页:Leo的博客 💞当前专栏:Docker系列 ✨特色专栏: MySQL学习 🥭本文内容: Docker | 自定义网络 📚个人知识库: 知识库,欢迎大家访问 1.前言 大家好,我是Leo哥…

最小体力消耗路径(广度优先搜索)

最小体力消耗路径 看见这题第一眼-动态规划&#xff0c;再看BFS。 用动态规划做的话不能一次保证当前位置能获得最小的最大值&#xff0c;因为需要周围的四个&#xff08;或者两个&#xff09;元素值。 这里我纯用的BFS&#xff0c;宽度优先搜索。类似于n皇后问题。见代码吧…