【多线程】单例模式和阻塞队列

news2025/4/25 17:14:59

目录

一.单例模式

1. 饿汉模式

2. 懒汉模式

 二.阻塞队列

1. 阻塞队列的概念

2. BlockingQueue接口

3.生产者-消费者模型

4.模拟生产者-消费者模型


一.单例模式

单例模式(Singleton Pattern)是一种常用的软件设计模式,其核心思想是确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。

为什么要引入单例模式?

单例模式的核心就是一个类中只有一个实例,因为只用管理一个实例,那么就可以更好的对代码进行一个校验和检查,方便高效管理,同时也避免了多个实例可能带来的问题。

如果创建一个实例需要耗费100G的资源,那么创建出多个实例,代价太大。而且在多数情况下,一个实例完全够用,所有没有必要创建出多个实例,这样就避免资源的重复创建和浪费


在编译器中,没有提供类只能创建出多少个实例的方法,但是我们可以通过一些代码逻辑去规定创建实例的要求,下面是常用的几种单例模式。

1. 饿汉模式

核心特点是 在类加载时就立即创建单例实例,并通过静态方法提供全局访问。

class Singleton{
    private static Singleton singleton = new Singleton();

    public static Singleton getSingleton(){
        return singleton;
    }

    //核心操作
    private Singleton(){}

}
  1. 使用static关键字保证唯一实例(在类被加载的时候就会创建出这个唯一实例)
  2. 构造方法被设为私有,导致构造方法无法被调用
  3. 如果想要获取这个实例只能使用静态方法getSingleton调用

 由于在类被加载的时候,就会创建出实例,创建实例的时机很早(感觉非常的迫切,像一个饿汉),所有叫做饿汉模式

 注意:在多线程中,并发调用getSingleton静态方法,由于只有读操作,所以是线程安全

缺点: 如果实例未被使用,实例依然会被创建,可能造成资源浪费(假设实例的大小是100G)。


2. 懒汉模式

其核心特点是 延迟实例的创建,只有在第一次使用时才初始化单例对象,以减少资源浪费。

public class LazySingleton {
    private static LazySingleton instance;
    private LazySingleton() {}

    // 非线程安全
    public static LazySingleton getInstance() {
        if (instance == null) {
   //这里会涉及指令重排序的问题
            instance = new LazySingleton();
        }
        return instance;
    }
}

在使用调用方法时,只有在第一次使用时才初始化实例,否则都是返回已经存在的实例


注意: 在多线程中,并发调用getInstance方法,由于同时存在两个线程修改一个变量操作,线程不安全

 发现出现new两次情况,所以解决这个问题,我们要进行加锁

class LazySingleton {
    private static LazySingleton instance;
    private LazySingleton() {}
    
    static Object A = new Object();

    // 非线程安全
    public static LazySingleton getInstance() {
        synchronized (A){
            if (instance == null) {
                //这里会涉及指令重排序的问题
                instance = new LazySingleton();
            }
        }

        return instance;
    }
}

 这里我们会发现,每次调用getInstance方法,都要进行加锁和解锁的步骤,这样的步骤开销很大

所以我们需要进行改进

    public static LazySingleton getInstance() {
        if(instance==null){
            synchronized (A){
                if (instance == null) {
                    //这里会涉及指令重排序的问题
                    instance = new LazySingleton();
                }
            }
        }
        return instance;
    }
  • 第一次的  if  语句判断是否需要加锁
  • 第二次的  if   语句判断是否为空

 写到这里我们的代码还存在一个很严重的问题,由于指令重排序引起的线程安全问题

                    instance = new LazySingleton();

 在创建一个实例的时候,主要有3步骤:正确的步骤顺序是1—>2—>3

1. 在内存中开辟一份空间2.使用构造方法去创建实例,3. 将空间的地址赋值给引用变量

但是JVM可能会将步骤的执行顺序发送改变1—>3—>2,从而引发线程安全问题


具体原因:t2线程没有进入因为锁阻塞这步,t2线程会在t1线程执行完地址赋值后,刚好执行第一次的 if 判断语句,发现引用变量不为空,会直接返回引用变量(但是引用变量的值是空的) 

其实解决也很简单使用volatile关键字

class LazySingleton {
    private static  volatile LazySingleton instance;
    private LazySingleton() {}

    static Object A = new Object();

    // 非线程安全
    public static LazySingleton getInstance() {
        if(instance==null){
            synchronized (A){
                if (instance == null) {
                    //这里会涉及指令重排序的问题
                    instance = new LazySingleton();
                }
            }
        }
        return instance;
    }
}

 二.阻塞队列

1. 阻塞队列的概念

阻塞队列可以看成是普通队列的一种扩展,遵循先进先出的原则,核心特性是当队列操作无法立即执行时,线程会被自动阻塞直到条件满足。

  • 如果队列是满的,进行入队列操作则会被阻塞,直到队列不满
  • 如果队列是空的,进行出队列操作则会被阻塞,直到队列不空

2. BlockingQueue接口

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

  • BlockingQueue接口属于java.util.concurrent包。它是线程安全的队列,支持阻塞操作。
  • 常见的实现类有:ArrayBlockingQueue,LinkedBlockingQueue,PriorityBlockingQueue
方法说明

put(E e)

队列未满时插入元素;若队列已满,则阻塞线程直到有空位。

take()

队列非空时取出元素;若队列为空,则阻塞线程直到有元素可用。

offer(E e)

队列未满时插入元素并返回 true;队列已满时直接返回 false(非阻塞)。

poll()

队列非空时取出元素并返回;队列为空时返回 null(非阻塞)。

offer(E e, long timeout, TimeUnit unit)

队列满时等待指定超时时间,超时后返回 false

poll(long timeout, TimeUnit unit)

队列空时等待指定超时时间,超时后返回 null

E peek()返回队列头部元素但不移除;队列空时返回 null

其中只有put( )和take( )方法带有阻塞的效果

3.生产者-消费者模型

生产者-消费者模型用于解决多线程环境下的线程同步问题,核心思想是通过 共享缓冲区(阻塞队列) 解耦生产者和消费者,使两者可以独立并发工作

生产者和消费者彼此之间不直接进行联系,而是通过缓冲区(阻塞队列)进行联系 

好处

(1)解耦合

  1. 解耦生产者和消费者的直接依赖,生产者和消费者可独立开发,提高开发效率,方便维护
  2.  生产者和消费者可并行执行,最大化利用 CPU、I/O 等资源
  3. 即使生产者挂了,也不会影响消费者的正常工作(反之同理)

解耦合在分布式系统中很常见,比如服务器的整个功能并不是由一个服务器全部完成,而是由多个服务器完成,每个服务器完成一部分功能,最后通过服务器之间的网络通信,实现整个功能 

(2)缓冲机制

  1. 生产者突发大量请求时,队列暂存数据,避免消费者过载崩溃。
  2. 消费者处理慢时,队列累积任务,避免生产者因等待而阻塞。
  3. 可以将缓冲区作为“蓄水池”,协调速度差异
public class Demo_3 {
    public static void main(String[] args) {

        BlockingDeque<Integer> deque = new LinkedBlockingDeque<>(100);
        Thread t1 = new Thread(()->{
            while(true){
                try {
                    Thread.sleep(300);
                    Random random = new Random();
                    int num = random.nextInt(101);
                    System.out.println("生产数:"+ num);
                    deque.put(num);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"生产者");

        t1.start();

        Thread t2 = new Thread(()->{
            while(true){
                try {
                    Thread.sleep(500);
                    int num = deque.take();
                    System.out.println("消费数:"+num);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"消费者");

        t2.start();
    }
}

 

4.模拟生产者-消费者模型

核心:阻塞队列的实现

将其看成一个循环队列,其中两个核心的方法:put()和take()

put():入队列操作,如果队列为满则进入阻塞状态,由take()进行唤醒

take():出队列操作,如果队列为空则进入阻塞状态,由put()方法进行唤醒

在判断是否需要进入阻塞状态的时候,使用while语句,进行多次判断,如果使用if语句相当于一锤定音,在阻塞的状态下,可能会被notifyAll()唤醒,但是这时候队列中的空间并不足够(虚假唤醒),也有可能会出现连续唤醒的情况,最好的方式是再进行一次判断

class MyBlockingQueue{

    Object A = new Object();
    int[] elems = null;
    int right ;
    int tail ;
    int usedSize;
    MyBlockingQueue(int capacity){
        elems = new int[capacity];
    }

    //注意锁的位置

    //放入操作
    public void put(int elem) throws InterruptedException {
        synchronized (A){
            //如果满,不能放出
            while (usedSize>=elems.length){
                A.wait();
            }
            //没有满,正常放入
            elems[tail++] = elem;
            if(tail>=elems.length){
                tail = 0;
            }
            usedSize++;
            A.notify();
        }

    }
    public int take() throws InterruptedException {
        synchronized (A){
            //如果为空,不能取出
            while (usedSize == 0){
                A.wait();
            }
            //不为空,正常取出
            int elem = elems[right++];
            //另一种写法
            right = right%elems.length;
            usedSize--;
            A.notify();
            return elem;
        }
    }

}

 点赞的宝子今晚自动触发「躺赢锦鲤」buff!

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

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

相关文章

Qt5.14.2+Cmake使用mingw64位编译opencv4.5成功图文教程

​ 一、下载安装相关编译环境软件 1.1 Python3.8&#xff1a;安装路径:C:\Users\Administrator\AppData\Local\Programs\Python\Python38-32 安装包&#xff1a;python3.8.exe 1.2 QT5.14.2&#xff1a;安装路径:C:\Qt\Qt5.14.2 1.3 opencv4.5&#xff1a;解压路径D:\o…

Mamba4D阅读

CVPR 2025 创新 基于transformer的4D主干由于其二次复杂度而通常存在较大的计算成本&#xff0c;特别是对于长视频序列。 开发了帧内空间Mamba模块&#xff0c;建立时空相关性。 GPU占用和速度很有优势。 代码还没发。 Pipeline 输入点云序列&#xff0c;根据超参数构建点管…

手工排查后门木马的常用姿势

声明&#xff01;本文章所有的工具分享仅仅只是供大家学习交流为主&#xff0c;切勿用于非法用途&#xff0c;如有任何触犯法律的行为&#xff0c;均与本人及团队无关&#xff01;&#xff01;&#xff01; 1. 检查异常文件 &#xff08;1&#xff09;查找最近修改的文件 # 查…

算法导论(动态规划)——简单多状态

算法思路&#xff08;17.16&#xff09; 状态表示&#xff1a; 在处理线性动态规划问题时&#xff0c;我们可以通过“经验 题目要求”来定义状态表示。通常有两种选择&#xff1a; 以某个位置为结尾的情况&#xff1b;以某个位置为起点的情况。 本题中&#xff0c;我们选择更常…

LeetCode 438. 找到字符串中所有字母的异位词

438. 找到字符串中所有字母的异位词 题目描述 给定两个字符串 s 和 p&#xff0c;找到 s 中所有 p 的 异位词 的子串&#xff0c;返回这些子串的起始索引。不考虑答案输出的顺序。 输入输出示例及数据范围 思路 这道题的思路其实很简单&#xff0c;就是一个滑动窗口的裸题&a…

java详细笔记总结持续完善

一.Java开发环境的搭建 1. 单位换算 1TB 1024GB 1GB 1024MB 1MB 1024KB 1KB 1024Byte (字节) 1Byte 8 bit(位) 注意&#xff1a;一个字节占8位 2. DOS命令 DOS : Disk Operation System 磁盘操作系统 即用于操作本地磁盘的系统 命令操作符号盘符切换命令盘符名:查看当前文…

wsl2的centos7安装jdk17、maven

JDK安装 查询系统中的jdk rpm -qa | grep java按照查询的结果&#xff0c;删除对应版本 yum -y remove java-1.7.0-openjdk*检查是否删除 java -version 下载JDK17 JDK17&#xff0c;下载之后存到wsl目录下&#xff08;看你自己&#xff09;然后一键安装 sudo rpm -ivh jd…

乐鑫ESP-Mesh-Lite方案,启明云端乐鑫代理商,创新组网拓展智能应用边界

在当今智能化浪潮的背景下&#xff0c;智能家居、智能农业、能源管理等领域对设备组网的需求日益增长。然而&#xff0c;传统的Wi-Fi组网方式常常受限于设备数量、路由器位置以及网络覆盖范围等因素&#xff0c;难以满足复杂场景下的多样化需求。 一方面&#xff0c;需要支持更…

ISIS【路由协议讲解】-通俗易懂!

IS-IS的背景 IS-IS最初是国际标准化组织ISO为它的无连接网络协议CLNP&#xff08;ConnectionLess Network Protocol&#xff09;设计的一种动态路由协议。随着TCP/IP协议的流行&#xff0c;为了提供对IP路由的支持&#xff0c;IETF在相关标准中对IS-IS进行了扩充和修改&#xf…

Vitis HLS 学习笔记--块级控制(IDE 2024.1 + 执行模式 + 默认接口实现)

目录 1. 简介 2. 默认接口实现 2.1 执行模式 2.2 接口范式 2.2.1 存储器 2.2.2 串流 2.3.3 寄存器 2.3 Vitis Kernel Flow 2.3.1 默认的协议 2.3.2 vadd 代码 2.3.3 查看报告 2.4 Vivado IP Flow 2.4.1 默认的协议 2.4.2 vadd 代码 2.4.3 查看报告 3. 测试与波…

红宝书第二十一讲:详解JavaScript的模块化(CommonJS与ES Modules)

红宝书第二十一讲&#xff1a;详解JavaScript的模块化&#xff08;CommonJS与ES Modules&#xff09; 资料取自《JavaScript高级程序设计&#xff08;第5版&#xff09;》。 查看总目录&#xff1a;红宝书学习大纲 一、模块化的意义&#xff1a;分而治之 模块化解决代码依赖混…

github 页面超时解决方法

github 页面超时解决方法 每次好不容易找到github项目代码之后&#xff0c;满心欢喜打开却是个无法访问&#xff0c;心顿时又凉了半截&#xff0c;现在有方法可以访问github啦 快来学习 打开浏览器插件&#xff08;Edge浏览器&#xff09; 搜索iLink插件 并安装 打开插件 填…

前端 vue 项目上线前操作

目录 一、打包分析 二、CDN加速 三、项目部署 1. 打包部署 2. nginx 解决 history 刷新 404 问题 3. nginx配置代理解决生产环境跨域问题 一、打包分析 项目编写完成后&#xff0c;就需要部署到服务器上供他人访问。但是在此之前&#xff0c;我们可以先预览项目的体积大…

vue: easy-cron扩展-更友好地显示表达式

我们一个批处理调度系统里要用到cron表达式&#xff0c;于是就在网上找到一个现成的组件easy-cron&#xff0c;采用后发现&#xff0c;它的配置界面还是很直观的&#xff0c;但显示时直接显示cron表达式&#xff0c;这对业务人员很不友好&#xff0c;所以&#xff0c;我们就扩展…

移动零+复写零+快乐数+盛最多水的容器+有效三角形的个数

前言 2025.3.31&#xff0c;今天开始每日五道算法题&#xff0c;今天的算法题如标题&#xff01; 双指针算法 在做今天的算法题之前&#xff0c;先来介绍一下今天会用到的算法&#xff01; 双指针算法分为了两种常见的形式&#xff1a;对撞指针和快慢指针&#xff01; 对撞…

Linux中常用的文件管理命令

一、文件和目录的建立 文件 touch命令 单一文件的创建 当按下回车后我们就可以在桌面获得一个名字叫file的文件 [rootlocalhost Desktop]# touch file 同步文件访问时间和文件修改时间 由上两图可知touch file这个命令还可以把文件访问时间和文件修改时间变成touch file命…

Root Cause Analysis in Microservice Using Neural Granger Causal Discovery

Root Cause Analysis in Microservice Using Neural Granger Causal Discovery 出处:AAAI 24 摘要 近年来,微服务因其可扩展性、可维护性和灵活性而在 IT 运营中得到广泛采用。然而,由于微服务中的复杂关系,当面临系统故障时,站点可靠性工程师 (SRE) 很难查明根本原…

学习笔记—数据结构—二叉树(链式)

目录 二叉树&#xff08;链式&#xff09; 概念 结构 初始化 遍历 前序遍历 中序遍历 后序遍历 层序遍历 结点个数 叶子结点个数 第k层结点个数 深度/高度 查找值为x的结点 销毁 判断是否为完整二叉树 总结 头文件Tree.h Tree.c 测试文件test.c 补充文件Qu…

深入理解指针5

sizeof和strlen的对比 sizeof的功能 **sizeof是**** 操作符****&#xff0c;用来**** 计算****变量或类型或数组所占**** 内存空间大小****&#xff0c;**** 单位是字节&#xff0c;****他不管内存里是什么数据** int main() {printf("%zd\n", sizeof(char));p…

一文详解QT环境搭建:Windows使用CLion配置QT开发环境

在当今的软件开发领域&#xff0c;跨平台应用的需求日益增长&#xff0c;Qt作为一款流行的C图形用户界面库&#xff0c;因其强大的功能和易用性而备受开发者青睐。与此同时&#xff0c;CLion作为一款专为C/C打造的强大IDE&#xff0c;提供了丰富的特性和高效的编码体验。本文将…