多线程案例(2)

news2024/11/24 10:25:23

文章目录

  • 多线程案例二
    • 二、阻塞式队列

大家好,我是晓星航。今天为大家带来的是 多线程案例二 相关的讲解!😀

多线程案例二

二、阻塞式队列

阻塞队列是什么

阻塞队列是一种特殊的队列. 也遵守 “先进先出” 的原则.

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

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

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

生产者消费者模型

生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。

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

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

比如在 “秒杀” 场景下, 服务器同一时刻可能会收到大量的支付请求. 如果直接处理这些支付请求, 服务器可能扛不住(每个支付请求的处理都需要比较复杂的流程). 这个时候就可以把这些请求都放 到一个阻塞队列中, 然后再由消费者线程慢慢的来处理每个支付请求.

这样做可以有效进行 “削峰”, 防止服务器被突然到来的一波请求直接冲垮.

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

解耦:降低耦合的过程。

耦合:两个东西关联程度高不高,高叫做高耦合,低叫做低耦合。

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

擀饺子皮的人不关心包饺子的人是谁(能包就行, 无论是手工包, 借助工具, 还是机器包), 包饺子的人 也不关心擀饺子皮的人是谁(有饺子皮就行, 无论是用擀面杖擀的, 还是拿罐头瓶擀, 还是直接从超 市买的).

使用生产者消费者模型前:

使用了生产者消费者模型之后:

标准库中的阻塞队列

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

  • BlockingQueue 是一个接口. 真正实现的类是 LinkedBlockingQueue.
  • put 方法用于阻塞式的入队列, take 用于阻塞式的出队列.
  • BlockingQueue 也有 offer, poll, peek 等方法, 但是这些方法不带有阻塞特性.
BlockingQueue<String> queue = new LinkedBlockingQueue<>();
// 入队列
queue.put("abc");
// 出队列. 如果没有 put 直接 take, 就会阻塞. 
String elem = queue.take();

生产者消费者模型

public static void main(String[] args) throws InterruptedException {
    BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<Integer>();
    Thread customer = new Thread(() -> {
        while (true) {
            try {
                int value = blockingQueue.take();
                System.out.println("消费元素: " + value);
           } catch (InterruptedException e) {
                e.printStackTrace();
           }
       }
   }, "消费者");
customer.start();
    Thread producer = new Thread(() -> {
        Random random = new Random();
        while (true) {
            try {
                int num = random.nextInt(1000);
                System.out.println("生产元素: " + num);
                blockingQueue.put(num);
                Thread.sleep(1000);
           } catch (InterruptedException e) {
                e.printStackTrace();
           }
       }
   }, "生产者");
    producer.start();
    customer.join();
    producer.join();
}

阻塞队列用法举例:

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
//阻塞队列的使用
public class ThreadDemo21 {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<String> blockingQueue = new LinkedBlockingQueue<>();
        blockingQueue.put("hello");
        String res = blockingQueue.take();
        System.out.println(res);
        res = blockingQueue.take();
        System.out.println(res);
    }
}

这里我们的res取出了hello之后,我们的阻塞队列就变为空了,因此我们第一次打印res就输出结果hello,而第二次打印我们程序就回因为阻塞队列无值而进入wait阻塞状态。

阻塞队列实现

  • 通过 “循环队列” 的方式来实现.
  • 使用 synchronized 进行加锁控制.
  • put 插入元素的时候, 判定如果队列满了, 就进行 wait. (注意, 要在循环中进行 wait. 被唤醒时不一 定队列就不满了, 因为同时可能是唤醒了多个线程).
  • take 取出元素的时候, 判定如果队列为空, 就进行 wait. (也是循环 wait)
public class BlockingQueue {
    private int[] items = new int[1000];
    private volatile int size = 0;
    private int head = 0;
    private int tail = 0;
    public void put(int value) throws InterruptedException {
        synchronized (this) {
            // 此处最好使用 while.
            // 否则 notifyAll 的时候, 该线程从 wait 中被唤醒,
            // 但是紧接着并未抢占到锁. 当锁被抢占的时候, 可能又已经队列满了
            // 就只能继续等待
            while (size == items.length) {
                wait();
           }
            items[tail] = value;
            tail = (tail + 1) % items.length;
            size++;
            notifyAll();
       }
   }
    public int take() throws InterruptedException {
        int ret = 0;
        synchronized (this) {
            while (size == 0) {
             wait();
           }
            ret = items[head];
            head = (head + 1) % items.length;
            size--;
            notifyAll();
       }
        return ret;
   }
    public synchronized int size() {
        return size;
   }
    // 测试代码
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue blockingQueue = new BlockingQueue();
        Thread customer = new Thread(() -> {
            while (true) {
                try {
                    int value = blockingQueue.take();
                    System.out.println(value);
               } catch (InterruptedException e) {
                    e.printStackTrace();
               }
           }
       }, "消费者");
        customer.start();
        Thread producer = new Thread(() -> {
            Random random = new Random();
            while (true) {
                try {
                    blockingQueue.put(random.nextInt(10000));
               } catch (InterruptedException e) {
                    e.printStackTrace();
               }
           }
       }, "生产者");
        producer.start();
        customer.join();
        producer.join();
   }
}

自己实现阻塞队列:ThreadDemo23

//自己写的阻塞队列
//此处不考虑泛型
class MyBlockingQueue {
    private final int[] items = new int[1000];
    private int head = 0;
    private int tail = 0;
    private int size = 0;

    //入队列
    public void put (int value) throws InterruptedException {
        synchronized (this) {
            //这里的 while 是为了循环判断这里的队列到底是否已满 如果不满足就一直阻塞
            while (size == items.length) {
                //数组已满 ,此时要产生阻塞
                //return;
                this.wait();
            }
            items[tail] = value;
            tail++;
            if (tail >= items.length) {
                tail = 0;
            }
            size++;

            //这个notify 唤醒 take 中的wait
            this.notify();
        }
    }
    //出队列 - 先进先出
    public Integer take() throws InterruptedException {
        int result = 0;
        synchronized (this) {
            //这里的 while 是为了循环判断这里的队列到底是否为空 如果不满足就一直阻塞
            while (size == 0) {
                //队列为空 无法出元素
                //return null;
                this.wait();
            }
            result = items[head];
            head++;
            if (head >= items.length) {
                head = 0;
            }
            size--;

            //唤醒 put 中的 wait
            this.notify();
        }
        return result;
    }
}
public class ThreadDemo23 {
    public static void main(String[] args) throws InterruptedException {
        MyBlockingQueue queue = new MyBlockingQueue();
        queue.put(1);
        queue.put(2);
        queue.put(3);
        queue.put(4);
        queue.put(5);
        int result = 0;
        result = queue.take();
        System.out.println("result: " + result);
        result = queue.take();
        System.out.println("result: " + result);
        result = queue.take();
        System.out.println("result: " + result);
        result = queue.take();
        System.out.println("result: " + result);
        result = queue.take();
        System.out.println("result: " + result);
    }
}

运行结果:

这里我们 put 和 take 中的wait和notify操作是相互唤醒的。

那么这两个线程中的 wait 是否可能同时触发???(如果同时触发了,显然就不能正确相互唤醒了)

答:不会,针对同一个队列,不可能即使满又是空。

感谢各位读者的阅读,本文章有任何错误都可以在评论区发表你们的意见,我会对文章进行改正的。如果本文章对你有帮助请动一动你们敏捷的小手点一点赞,你的每一次鼓励都是作者创作的动力哦!😘

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

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

相关文章

设计走查指南:提升设计质量的关键步骤

在产品设计过程中&#xff0c;确保产品设计质量是至关重要的。设计走查作为一种关键的质量控制方法&#xff0c;能够帮助设计团队发现问题并采取相应措施来提升设计质量。通过有效地进行设计走查&#xff0c;团队可以提高设计作品的一致性、可用性和用户满意度&#xff0c;从而…

正则表达式在格式校验中的应用以及包装类的重要性

文章目录 正则表达式&#xff1a;做格式校验包装类&#xff1a;在基本数据类型与引用数据类型间的桥梁总结 在现代IT技术岗位的面试中&#xff0c;掌握正则表达式的应用以及理解包装类的重要性是非常有益的。这篇博客将围绕这两个主题展开&#xff0c;帮助读者更好地面对面试挑…

Elasticsearch Java客户端和Spring data elasticsearch-Elasticsearch文章三

文章目录 官网版本组件版本说明实现代码地址es Spring Data Repositories例子&#xff1a;ElasticsearchRepository分析 es Spring Data Repositories 关键字es Spring Data Repositories client 加载rest风格客户端直接执行dsl例子响应式客户端-ReactiveElasticsearchClientpo…

【SLAM】LoFTR知多少

1. LoFTR: Detector-Free Local Feature Matching with Transformers PAPER 论文 | LoFTR: Detector-Free Local Feature Matching with Transformers 代码 | CODE: 关键词 | detector-free, local feature matching LoFTR知多少 1. LoFTR: Detector-Free Local Feature M…

DirectX SDK下载安装及开发环境设置

1 DirectX DirectX&#xff08;Direct eXtension&#xff0c;简称DX&#xff09;是由微软公司创建的多媒体编程接口&#xff0c;是一种应用程序接口&#xff08;API&#xff09;。DirectX可以让以windows为平台的游戏或多媒体程序获得更高的执行效率&#xff0c;加强3D图形和声…

15、两个Runner初始化器(ApplicationRunner接口和CommandLineRunner接口的实现类)

两个Runner初始化器 两个Runner初始化器——主要作用是对component组件来执行初始化 这里的Component组件我理解为是被Component注解修饰的类 Component //用这个注解修饰的类&#xff0c;意味着这个类是spring容器中的一个组件&#xff0c;springboot应用会自动加载该组件。 …

python-网络爬虫.regular

regular 正则表达式 (regular expression) 正则表达式(regular expression)描述了一种字符串匹配的模式 &#xff08;pattern&#xff09;&#xff0c; 可以用来检查一个串是否含有某种子串、将匹配的子串替换或者从某个串 中取出符合某个条件的子串等。 正则表达式是由普通…

gitlab配置webhook

一.前言 当需要做jenkins的自动化触发构建时&#xff0c;就需要配置gitlab的webhook功能&#xff0c;以下来展示以下如何配置gitlab的webhook&#xff0c;jenkins的配置就不在这里展示了&#xff0c;可以去看我devops文章的完整配置 二.配置 在新版本的gitlab中&#xff0c…

MySQL对表的操作以及数据类型

文章目录 创建删除表查看修改重命名表新增列修改列的属性删除列修改列名插入数据 数据类型enum和setenum和set的查找 创建 create table table_name ( field1 datatype, field2 datatype, field3 datatype ) charset 字符集 collate 校验规则 engine 存储引擎;其中field 表示列…

Linux - 环境变量

1.基本概念 环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数 如&#xff1a;我们在编写C/C代码的时候&#xff0c;在链接的时候&#xff0c;从来不知道我们的所链接的动态静态库在哪里&#xff0c;但 是照样可以链接成功&#xff0c;生…

超级个体新时代Web3space西南旗舰合伙人招募活动圆满落幕

7月30日&#xff0c;一场备受瞩目的超级个体新时代—Web3space西南旗舰合伙人招募活动在四川省成都市G1咖啡馆会议室成功举办。本次活动吸引了30余位Web3领域的从业者和爱好者参与&#xff0c;现场氛围十分热烈。 首先&#xff0c;CyberDAO执行合伙人JR老师主持了Web3space商业…

【AI实战】开源中文 llama2 来了,30 分钟搭建 130 亿参数大模型 Llama2-Chinese-13b-Chat

【AI实战】开源中文 llama2 来了&#xff0c;30 分钟搭建 130 亿参数大模型 Llama2-Chinese-13b-Chat 简介环境配置环境搭建依赖安装 代码及模型权重拉取拉取 Llama2-Chinese拉取 Llama2-Chinese-13b-Chat 模型权重及代码 终端测试页面测试安装 gradio加载模型并启动服务 国内 …

配置VS Code 使其支持vue项目断点调试

起因 每个应用&#xff0c;不论大小&#xff0c;都需要理解程序是如何运行失败的。当我们写的程序没有按照自己写的逻辑走的时候&#xff0c;我们就会逐步一一排查问题。在平常开发过程中我们可能会借助 console.log 来排查,但是现在我们可以借助 VS Code 断点来调试项目。 前…

Linux下查阅帮助文档必学命令 man

Linux操作系统的使用中,我们经常会遇到很多问题,这个时候查询文档的能力至关重要,黄老师来推荐大家使用man,这时我们必须掌握的查阅能力: 最常用的命令: man 名称 man 数字(1~9) 名称 这里的数字分别代表:

JavaWeb 项目实现(四) 验证旧密码

1.验证旧密码 步骤很简单&#xff0c;从Session中取到当前密码&#xff0c;和修改密码界面得到的旧密码对比&#xff0c;判断是否相等。 特别之处在于实现用到了Ajax&#xff0c;可以不刷新整个页面的情况下与Web服务器进行通信。 2.Ajax Ajax&#xff08;Asynchronous Java…

使用Gunicorn+Nginx部署Flask项目

部署-开发机上的准备工作 确认项目没有bug。用pip freeze > requirements.txt将当前环境的包导出到requirements.txt文件中&#xff0c;方便部署的时候安装。将项目上传到服务器上的/srv目录下。这里以git为例。使用git比其他上传方式&#xff08;比如使用pycharm&#xff…

深度学习之用PyTorch实现线性回归

代码 # 调用库 import torch# 数据准备 x_data torch.Tensor([[1.0], [2.0], [3.0]]) # 训练集输入值 y_data torch.Tensor([[2.0], [4.0], [6.0]]) # 训练集输出值# 定义线性回归模型 class LinearModel(torch.nn.Module):def __init__(self):super(LinearModel, self)._…

nodejs安装及多版本安装与TS环境搭建

nodejs安装及多版本安装与TS环境搭建 方法一&#xff1a; 普通安装nodejs,确定只能安装一个。网址&#xff1a;链接: 官网 不同系统下安装&#xff1a;不同系统下的nodejs 方法二&#xff1a; 借助工具nvm&#xff0c;安装多个nodejs&#xff0c;随时切换nodejs版本 什么是…

禁止别人调试自己的前端页面代码

✨ 目录 &#x1f388; 为啥要禁止&#xff1f;&#x1f388; 无限 debugger&#x1f388; 无限 debugger 的对策&#x1f388; 禁止断点的对策&#x1f388; 忽略执行的代码&#x1f388; 忽略执行代码的对策&#x1f388; 终极增强防调试代码 &#x1f388; 为啥要禁止&#…

简约好看的帮助中心创建案例,赶紧点赞收藏!

在线帮助中心创建案例是提供用户支持和解决问题的有效方式之一。一个简约好看的帮助中心案例能够帮助用户快速找到需要的信息并解决问题&#xff0c;同时也能提升用户体验&#xff0c;增加点赞和收藏的可能性。 帮助中心创建案例分享&#xff1a; 酷学院&#xff1a; 酷渲&a…