多线程(5)——单例模式,阻塞队列

news2025/6/1 10:05:17

目录

  • 单例模式
    • 饿汉模式
    • 懒汉模式—单线程版
    • 懒汉模式—多线程版(经典面试题)
    • 懒汉模式—多线程版(改进)
  • 阻塞队列
    • 阻塞队列是什么
    • 生产者消费者模型
    • 标准库中的阻塞队列-BlockingQueue
    • 阻塞队列实现


单例模式

单例模式是一种设计模式,用于解决分工协作的问题
单例,一个程序(进程中)唯一实例

设计模式相当于大佬给了一个模版(套路),按照这个模板写,代码效率能比较高
它能解决一些固定场景的固定套路
设计模式还有很多种,单例模式只是其中之一

单例模式指单个实例(对象)
正常情况下,一个类能创建多个实例,但在有些场景中,我们只希望这个类只有一个实例

饿汉模式

饿汉模式是线程安全的
饿汉模式实现:
在这里插入图片描述
由于此处instance 是一个 static 成员,创建时机就是在类加载的时候(近似理解为程序一启动),常见紧迫

懒汉模式—单线程版

它是线程不安全的,如果有多个线程调用getInstance ,多个判定==null,可能会创建多个实例
getInstance 可能会出现多个线程对同一个变量进行修改,而如果只是读取,就是线程安全的(饿汉模式)
在这里插入图片描述
在这里插入图片描述
实际开发中,单例类的构造方法可能是一个非常重量的方法,如果多次触发这个bug,就会浪费好几分钟的时间(加载时间翻倍)
懒汉模式就好比非必要不洗碗

假设有一个非常大的文件(几个GB)
要打开这个文件查看内容,两个方案
1.把整个文件都加载到内存里,然后再给用户显示(饿汉模式)
2.值加载文件的一小部分,就立即给用户显示,随着用户的翻页操作,在加载对应部分数据(懒汉模式)
饿汉模式会让用户使用前等待一段时间(明显卡顿),懒汉模式则不需要,因此懒汉模式泛用更广

懒汉模式—多线程版(经典面试题)

对上述代码改动
1.
在这里插入图片描述
显然不可以
上面单线程的问题是判定null 的时候就出现问题了,所以应该把判定null 和加锁操作打包成一个原子

在这里插入图片描述
上述创建实例只在线程第一次调用的时候才涉及到线程安全问题,但是上面代码在每次调用 getInstance 都加了一次锁,显然很耗费资源,所以还要进行一次变动
3.
在这里插入图片描述
为什么会出现一模一样的线程,连续写两遍?
在之前,都是按照单线程来编写代码,这个代码执行下来,第一次判定和第二次判定的结论是一定相同的,但是现在是多线程模式了,第一次判定和第二次判定,结论可以不同
因为在第一次和第二次判定之间,可能有另一个线程修改了 instance

4.指令重排序引起的线程安全问题
在这里插入图片描述

还是之前的代码,上述new 操作设计到的指令是非常多的,为了简化这个模型,我们分成三步:
1. 申请内存空间
2. 在内存空间上进行初始化(构造方法)
3. 内存地址,保存到引用变量中(instance)

在内部操作中,步骤可能是按照1 2 3 执行,也可能是按照1 3 2 执行
就像
1.买一个房子
2.装修
3.交钥匙
2和3可以随意交换

在这里插入图片描述
虽然 t1 此时持有锁,但是由于 t2 没有进行加锁操作,t2 不会有任何阻塞,直接返回了

解决方法:volatile
在这里插入图片描述

经典面试题:

  1. 加锁的原因
  2. 第二次判断非空的原因
  3. 加volatile 的原因
    在这里插入图片描述

懒汉模式—多线程版(改进)

class SingleTonLazy{
    private static volatile SingleTonLazy instance=null;
    private static Object locker=new Object();
    //第一次使用这个实例才创建实例,创建时机更晚
    public static SingleTonLazy getInstance(){
        if(instance==null){
            synchronized (locker){
                if(instance==null){
                    instance=new SingleTonLazy();
                }
            }
        }
        return instance;
    }
}

阻塞队列

阻塞队列是什么

阻塞队列是一种特殊的队列,它也是先进先出的

与其他队列相比,它的特点:
1 线程安全
2. 带有阻塞功能

1.如果队列为空,尝试出队列,会触发阻塞,直到队列不空
2.如果队列满了,尝试入队列,也会触发阻塞,直到队列不满

阻塞队列的⼀个典型应⽤场景就是"⽣产者消费者模型".这是⼀种⾮常典型的开发模型

生产者消费者模型

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

⽣产者和消费者彼此之间不直接通讯,⽽通过阻塞队列来进⾏通讯,所以⽣产者⽣产完数据之后不⽤
等待消费者处理,直接扔给阻塞队列,消费者不找⽣产者要数据,⽽是直接从阻塞队列⾥取.

比如加工出一个食物,加工者就是生产者,吃(消耗)这个食物的人就是消费者
像“食物” 就要有存放的地方,用于存储和消耗,阻塞队列就起到了这个作用

引入生产者消费者模型目的是为了减少“锁竞争”
因为生产者和消费者的步调不一定完全一致,通过阻塞队列就能协调好两者关系

生产者消费者模型优点:

  1. 减少资源竞争,提高效率
  2. 可以更好做到模块之间的解耦合
    例如 A服务器要传给 B服务器 某个信息,可以通过阻塞队列存储信息,然后让B服务器自己去拿,这样耦合度就降低了,B变动,A就不需要修改
  3. 削峰填谷
    在这里插入图片描述

但是这个模型还有一些缺点
1.系统结构更复杂
2.引入队列的层数太多,效率会受到影响

标准库中的阻塞队列-BlockingQueue

因为BlockingQueue 是一个接口interface ,因此不能实例new这个队列,而是实例化继承了这个队列的类

BlockingQueue<String> queue=new LinkedBlockingQueue<>();
//基于链表实现的阻塞队列

BlockingQueue<String> queue1=new ArrayBlockingQueue<>(100);
//基于数组实现的阻塞队列,它必须指定容量

入队列和出队列:

注意虽然BlockingQueue 有offer,add等方法,但是这些方法不带有“阻塞功能”

queue.put("123");//入队列
String s=queue.take();//出队列

观察阻塞队列:
在这里插入图片描述
因为队列最大容量只有2,如果再入队列,就会触发阻塞了,take() 同理

阻塞队列使用:
在这里插入图片描述

阻塞队列实现

/假设元素类型是String
//基于数组实现队列
class MyBlockingQueue{
    public int head=0;
    public int tail=0;
    public int size=0;
    private Object lock=new Object();
    public String[] array=null;
    public MyBlockingQueue(int capacity){
        array=new String[capacity];
    }

    public void put(String elem) throws InterruptedException {
        synchronized (lock){
            while (size>=array.length){//队列满了,触发阻塞
                lock.wait();//等待take后唤醒
            }
            array[tail++]=elem;
            size++;
            if(tail>=array.length){
                tail=0;
            }
            lock.notify();//唤醒take的阻塞
        }
    }
    public String take() throws InterruptedException {
        synchronized (lock){
            while (size<=0){//队列为空,触发阻塞
                lock.wait();//等待put后唤醒
            }
            String result=array[head];
            head++;size--;
            if(head>=array.length){
                head=0;
            }
            lock.notify();//唤醒put
            return result;
        }
    }
}

此处有几个注意事项:
在这里插入图片描述

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

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

相关文章

视频监控汇聚平台EasyCVR工业与安全监控:防爆摄像机的安全应用与注意事项

石油、化工、煤矿等行业存在易燃易爆气体、粉尘&#xff0c;普通监控设备易因电火花、高温引发爆炸火灾。随着工业规模扩大&#xff0c;安全生产监控需求激增&#xff0c;防爆摄像机成为保障安全的关键。加之国家法规与行业标准对危险环境监控设备要求严格&#xff0c;规范其应…

基于 Redis 实现分布式锁:原理及注意事项

文章目录 基于 Redis 实现分布式锁&#xff1a;原理及注意事项基于 Redis 实现分布式锁的原理Redis 分布式锁的过期时间和锁续期机制如何防止锁被其他 goroutine 删除&#xff1f;Redis 分布式锁存在的单点故障问题&#xff1a;基于 RedLock 的解决方案高并发场景中 Redis 分布…

手机设备多?怎样设置IP保证不关联

在移动互联网时代&#xff0c;多设备运营&#xff08;如电商、游戏工作室、社交媒体矩阵&#xff09;常面临IP关联风险&#xff0c;轻则账号受限&#xff0c;重则封禁。以下提供6种高效设置独立IP的方法&#xff0c;结合技术原理与实操建议&#xff0c;助您打造稳定合规的运营环…

哈尔滨工业大学计算机系统大作业程序人生-Hello’s P2P

摘 要 文章以C语言程序设计经典案例hello.c为研究对象&#xff0c;系统解析程序在计算机系统中的完整生命周期。剖析源代码通过预处理、编译、汇编、链接四阶段演化为可执行目标程序的编译系统工作机制&#xff0c;继而从进程视角揭示程序运行时计算机体系结构的协同运作&…

Linux系统管理与编程24:基础条件准备-混搭“本地+阿里云”yum源

兰生幽谷&#xff0c;不为莫服而不芳&#xff1b; 君子行义&#xff0c;不为莫知而止休。 1.添加宿主机共享文件夹 Linux虚拟机可以和宿主机共享文件夹&#xff0c;这样有利于工具文件的共享。具体操作如下&#xff1a; 1&#xff09;vmware workstation共享文件夹 虚拟机…

如何在 Windows 10 PC 上获取 iPhone短信

您可以轻松地将媒体数据从 iPhone 传输到 Windows 计算机&#xff0c;并直接访问计算机上的数据。但是&#xff0c;您可以在 Windows 10 PC 上接收 iPhone 短信吗&#xff1f;有什么功能或工具支持它吗&#xff1f;如果您发现在 Windows 10 PC 上接收 iPhone 消息很困难&#x…

Linux 系统中的软链接与硬链接

目录 一、什么是软链接&#xff1f; 1. 创建软链接 2. 软链接的特性 3. 软链接的用途 二、什么是硬链接&#xff1f; 1. 创建硬链接 2. 硬链接的特性 3. 硬链接的用途 4. 目录硬链接的特殊性 ​编辑 三、软链接与硬链接的区别 1. inode 编号 2. 路径依赖 3. 删除行…

Python爬虫第22节- 结合Selenium识别滑动验证码实战

目录 一、引言 二、滑动验证码原理与反爬机制 2.1 验证码原理 2.2 反爬机制 三、工程实战&#xff1a;滑动验证码识别全流程 3.1 工程准备 3.1.1 环境依赖 3.1.2 目标网站与验证码识别案例 3.2 核心破解流程 3.2.1 自动化打开网页与登录 3.2.2 获取验证码图片&#…

Escrcpy(安卓手机投屏软件) v1.29.6 中文绿色版

在数字设备日益普及的今天&#xff0c;用户对于设备的控制和管理需求也在不断增加。对于Android设备用户来说&#xff0c;Escrcpy这款强大的工具无疑是一个福音。它不仅提供了直观的图形化界面&#xff0c;让用户能够轻松显示和控制自己的Android设备&#xff0c;还以完全免费开…

Linux:深入理解网络层

网络层在复杂的网络环境中确定一个合适的路径.传输到指定的网络中 一、网络层的理解 问题1&#xff1a;为什么要有网络层的概念呢&#xff1f;&#xff1f; ——>我们先来讲一个故事&#xff1a; 假设我在学校里被誉为数学大神&#xff0c;是因为我的数学有考满分的能力&…

Linux_编辑器Vim基本使用

✨✨ 欢迎大家来到小伞的大讲堂✨✨ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属专栏&#xff1a;LInux_st 小伞的主页&#xff1a;xiaosan_blog 制作不易&#xff01;点个赞吧&#xff01;&#xff01;谢谢喵&#xff01;&a…

vue展示修改前后对比,并显示修改标注diff

动态父组件 <template><el-buttontype"primary"size"small"plainclick"showDiffDialog(subItem)">查看修改内容</el-button><TextDiffDialogv-model:visible"diffDialogVisible":before"currentDiffItem?.…

LiveWallpaperMacOS:让你的 Mac 桌面动起来

随着桌面美化需求的不断提升,用户对于桌面壁纸的要求已经不再局限于静态图片。越来越多的 Mac 用户希望桌面能像 Windows 一样,拥有动态壁纸,展现个性、提升体验。LiveWallpaperMacOS 正是这样一款让你的 Mac 桌面焕发活力的开源项目。 本文将详细介绍 LiveWallpaperMacOS …

[预训练]Encoder-only架构的预训练任务核心机制

原创文章1FFN前馈网络与激活函数技术解析&#xff1a;Transformer模型中的关键模块2Transformer掩码技术全解析&#xff1a;分类、原理与应用场景3【大模型技术】Attention注意力机制详解一4Transformer核心技术解析LCPO方法&#xff1a;精准控制推理长度的新突破5Transformer模…

07-后端Web实战(部门管理)

5. 修改部门 对于任何业务的修改功能来说&#xff0c;一般都会分为两步进行&#xff1a;查询回显、修改数据。 5.1 查询回显 5.1.1 需求 当我们点击 "编辑" 的时候&#xff0c;需要根据ID查询部门数据&#xff0c;然后用于页面回显展示。 5.1.2 接口描述 参照参照…

mysql ACID 原理

序言&#xff1a;ACID 是一组数据库设计原则&#xff0c;他是业务数据和关键业务程序的可靠性保障。 1、atomicity&#xff08;原子性&#xff09; 依赖如下能力 autocommit commit rollback2、一致性 2.1 double write buffer 1、定义&#xff1a;double write buffer 是…

[Rust_1] 环境配置 | vs golang | 程序运行 | 包管理

目录 Rust 环境安装 GoLang和Rust 关于Go 关于Rust Rust vs. Go&#xff0c;优缺点 GoLang的优点 GoLang的缺点 Rust的优点 Rust的缺点 数据告诉我们什么&#xff1f; Rust和Go的主要区别 (1) 性能 (2) 并发性 (3) 内存安全性 (4) 开发速度 (5) 开发者体验 Ru…

二十五、面向对象底层逻辑-SpringMVC九大组件之HandlerMapping接口设计

一、引言&#xff1a;MVC架构的交通枢纽 在Spring MVC框架中&#xff0c;HandlerMapping接口扮演着"请求导航仪"的关键角色&#xff0c;它决定了HTTP请求如何被路由到对应的Controller处理器。作为MVC模式的核心组件之一&#xff0c;HandlerMapping在请求处理的生命…

HUAWEI交换机配置镜像口验证(eNSP)

技术术语&#xff1a; 流量观察口&#xff1a;就是我们常说的镜像口&#xff0c;被观察的流量的引流目的端口 流量源端口&#xff1a;企业生产端口&#xff0c;作为观察口观察对象。 命令介绍&#xff1a; [核心交换机]observe-port [观察端口ID或编号&#xff08;数字&am…

前端vue3实现图片懒加载

场景和指令用法 场景:电商网站的首页通常会很长&#xff0c;用户不一定能访问到页面靠下面的图片&#xff0c;这类图片通过懒加载优化手段可以做到只有进入视口区域才发送图片请求 核心原理:图片进入视口才发送资源请求 首先&#xff1a;我们需要定义一个全局的指令&#x…