多线程案例-阻塞队列

阻塞队列是什么

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

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

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

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

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

生产者消费者模型

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

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

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

示意图如下:

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.

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

LinuxC中进程通信

LinuxC中进程通信 信号&#xff08;Signals&#xff09;&#xff1a;Linux 提供了信号机制&#xff0c;允许一个进程向另一个进程发送信号以通知特定事件的发生。这是一种轻量级的通信机制&#xff0c;通常用于处理异步事件。您可以使用 kill 命令或 kill 函数来发送信号&…

day16_java多线程(入门了解)

多线程入门 一、线程和进程 进程 进程&#xff1a;是指一个内存中运行的应用程序&#xff0c;每个进程都有一个独立的内存空间和系统资源&#xff0c;一个应用程序可以同时运行多个进程&#xff1b;进程也是程序的一次执行过程&#xff0c;是系统运行程序的基本单位&#xff1…

从这三个方面,可以快速分析光伏系统设计方案的可行性!

随着光伏技术的不断发展&#xff0c;光伏项目也越来越受欢迎。光伏发电是利用半导体界面的光生伏特效应而将光能直接转变为电能的一种技术。如何分析光伏系统设计方案的可行性&#xff1f; 1.经济可行性分析 需要考虑光伏系统的投资成本&#xff0c;包括太阳能电池板、逆变器…

Qt/QML编程学习之心得:工程中的文件(十二)

Qt生成了工程之后,尤其在QtCreator产生对应的project项目之后,就如同VisualStudio一样,会产生相关的工程文件,那么这些工程文件都是做什么的呢?这里介绍一下。比如产生了一个Qt Widget application,当然如果Qt Quick Application工程会有所不同。 一、.pro和.pro.user …

Java学习总结

1. Java集合体系框架 java.util中包含 Java 最常用的the collections framework。 Java集合类主要由两个根接口Collection和Map派生出来的。 Collection 接口派生出了三个子接口List、Set、Queue。Map 接口 因此Java集合大致也可分成List、Set、Queue、Map四种接口体系。 …

数据库常用锁

锁是计算机在执行多线程或线程时用于并发访问同一共享资源时的同步机制&#xff0c;MySQL中的锁是在服务器层或者存储引擎层实现的&#xff0c;保证了数据访问的一致性与有效性。 MySQL锁可以按模式分类为&#xff1a;乐观锁与悲观锁。 按粒度分可以分为全局锁、表级锁、页级…

计算整数各位数字之和 C语言xdoj29

时间限制: 1 S 内存限制: 1000 Kb 问题描述: 假设n是一个由最多9位数字&#xff08;d9, …, d1&#xff09;组成的正整数。编写一个程序计算n的每一位数字之和 输入说明: 输入数据为一个正整数n 输出说明: 对整数n输出它的各位数字之和后换行 输入样例: …

Android渲染-AHardwareBuffer

本文主要从应用的角度介绍android的native层AHardwareBuffer创建纹理以及保存渲染数据。 HardwareBuffer 要介绍native层的AHardwareBuffer&#xff0c;就需要先从Java层的HardwareBuffer说起。Android官方对于HardwareBuffer介绍如下&#xff1a; HardwareBuffer wraps a na…

【lesson11】数据类型之string类型

文章目录 数据类型分类string类型set类型测试 enum类型测试 string类型的内容查找找所有女生&#xff08;enum中&#xff09;找爱好有游泳的人&#xff08;set中&#xff09;找到爱好中有足球和篮球的人 数据类型分类 string类型 set类型 说明&#xff1a; set&#xff1a;集…

uniCloud(一) 新建项目、初始化服务空间、云对象访问测试

一、新建一个带有unicloud 二、创建一个服务空间 1. 右键uniCloud&#xff0c;关联云服务空间 我当前没有服务空间&#xff0c;需要新建一个服务空间&#xff0c;之后将其关联。初始化服务空间需要的时间有点长 服务空间初始化成功后&#xff0c;刷新HBuilder&#xff0c;勾选…

GitHub 开源开发者日,沙龙见闻与洞察

前言 12月10日&#xff0c;我有幸受邀参加了 GitHub Universe 2023 Watch Party in Shanghai – 开源开发者日。 这次活动在上海微软 Reactor 线下举行&#xff0c;与数位 AI 及开源大咖汇聚现场&#xff0c;与开源爱好者们共同畅聊开源之旅。 活动介绍 整个线下大会中&#…

OpenCV-python numpy使用和基本作图

文章目录 一、实验目的二、实验内容三、实验过程Numpy1.NumPy 操作2.NumPy Ndarray 对象3.NumPy 基本类型4.NumPy 数组属性ndarray.ndimndarray.shapendarray.itemsizendarray.flags 5.NumPy 创建数组numpy.emptynumpy.zerosnumpy.ones 6.NumPy 从已有的数组创建数组numpy.asar…

openGauss学习笔记-152 openGauss 数据库运维-备份与恢复-物理备份与恢复之PITR恢复

文章目录 openGauss学习笔记-152 openGauss 数据库运维-备份与恢复-物理备份与恢复之PITR恢复152.1 背景信息152.2 前提条件152.3 PITR恢复流程152.4 recovery.conf文件配置**152.4.1 归档恢复配置****152.4.2 恢复目标设置** openGauss学习笔记-152 openGauss 数据库运维-备份…

微信小程序 长按录音+录制视频

<view class"bigCircle" bindtouchstart"start" bindtouchend"stop"><view class"smallCircle {{startVedio?onVedio:}}"><text>{{startVedio?正在录音:长按录音}}</text></view> </view> <…

如何使用Imagewheel本地搭建一个简单的的私人图床公网可访问?

文章目录 1.前言2. Imagewheel网站搭建2.1. Imagewheel下载和安装2.2. Imagewheel网页测试2.3.cpolar的安装和注册 3.本地网页发布3.1.Cpolar临时数据隧道3.2.Cpolar稳定隧道&#xff08;云端设置&#xff09;3.3.Cpolar稳定隧道&#xff08;本地设置&#xff09; 4.公网访问测…

前后端请求之nginx配置

问题&#xff1a; 前端发送的请求&#xff0c;是如何请求到后端服务器的&#xff1f; 如&#xff0c;前端请求地址&#xff1a;http://loclhost/api/employee/login&#xff1a; 后端相应请求地址&#xff1a;http://loclhost:8080/admin/employee/login 回答&#xff1a; …

vue 限制在指定容器内可拖拽的div

<template><div class"container" id"container"><div class"drag-box center" v-drag v-if"isShowDrag"><div>无法拖拽出容器的div浮窗</div></div></div> </template><script&g…

奇点云2023数智科技大会来了,“双12”直播见!

企业数字化进程深入的同时&#xff0c;也在越来越多的新问题中“越陷越深”&#xff1a; 数据暴涨&#xff0c;作业量和分析维度不同以往&#xff0c;即便加了机器&#xff0c;仍然一查就崩&#xff1b; 终于搞定新增渠道数据的OneID融合&#xff0c;又出现几个渠道要变更&…

二十、FreeRTOS之Tickless低功耗模式

本节需要掌握以下内容&#xff1a; 1&#xff0c;低功耗模式简介&#xff08;了解&#xff09; 2&#xff0c; Tickless模式详解&#xff08;熟悉&#xff09; 3&#xff0c; Tickless模式相关配置项&#xff08;掌握&#xff09; 4&#xff0c;Tickless低功耗模式实验&…

佳明(Garmin) fēnix 7X 增加小睡检测功能

文章目录 &#xff08;一&#xff09;零星小睡&#xff08;二&#xff09;小睡检测&#xff08;三&#xff09;吐槽佳明&#xff08;3.1&#xff09;心率检测&#xff08;3.2&#xff09;光线感应器&#xff08;3.3&#xff09;手表重量&#xff08;3.4&#xff09;手表续航 &a…