【多线程奇妙屋】你听说过设计模式吗?软件开发中可全局访问一个对象的设计模式——单例模式,工作常用, 建议收藏 ! ! !

news2024/12/26 11:16:29

本篇会加入个人的所谓鱼式疯言

❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言

而是理解过并总结出来通俗易懂的大白话,

小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的.

🤭🤭🤭可能说的不是那么严谨.但小编初心是能让更多人能接受我们这个概念 !!!

在这里插入图片描述

引言

在多线程的世界里,单例模式如同一颗闪耀的明星,它确保了唯一的存在,犹如黑夜中的明灯,指引着程序的正确运行。让我们一同揭开单例模式的神秘面纱,探索它在多线程中的奇妙之处。

目录

  1. 单例模式

  2. 单例模式的简单实现

  3. 单例模式的多线程

一. 单例模式

在这里插入图片描述

1. 设计模式的初识

设计模式是在 软件开发 中所常用的一种 实施方案和策略

就相当于小伙伴们在写 算法 时 , 就需要 某种特点的数据结构的对于数据的组织方式 来帮助一样。

可能小伙伴们都在传设计模式有23种, 但是不一定说就只有23种, 只是说常见常用到的设计模式有23种。

设计模式的就好像 棋谱 , 设计模式补齐一些能力不是很出众的程序猿的短板,

并且对于 主流语言是刚刚好的 , 对于一些 不是很主流的语言设计模式也提供更好的支撑

2. 单例模式的初识

小伙伴们是否想过, 声明一个 之后, 是否可以在全局只 使用一个对象(实例) 的方式, 而不能使用 很多个对象(实例) 来实现我们的 业务逻辑

所以才出现了单例模式。

单例模式 : 只允许 实例化一个对象 , 在全局范围内也只能 访问实例化的这一个对象

单例模式的实现方式很多样, 但是主流的实现单例模式的方式主要是以下两种:

  • 饿汉模式 : 先在 类中实例好唯一的一个对象 ,当 用户需要 时就通过方法来访问这 唯一的对象

  • 懒汉模式不在类中实例化对象 , 当用户需要时, 如果 没有实例化好对象就先实例化对象然后访问 ,如果有就 直接访问


初步了解完这 两种模式 ,下面就让小编真正带着小伙伴通过代码的方式来实现吧 ~ ~ ~

二. 单例模式的简单实现

1. 饿汉模式

在这里插入图片描述

<1>. 代码演示

package TestDemo6;

// 创建一个单例的类
// 饿汉方式实现.
// 饿 的意思是 "迫切"
// 在类被加载的时候, 就会创建出这个单例的实例
class Singleton {
    private static Singleton instance = new Singleton();

    public static Singleton getInstance() {
        return instance;
    }

    // 单例模式的最关键部分.
    private Singleton() { }
}

public class Demo24 {
    public static void main(String[] args) {
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        System.out.println(s1 == s2);

    }
}

在这里插入图片描述

<2>. 流程阐述

  1. 首先声明一个 单例模式的类
  1. 然后在这个类中创建一个私有的静态成员变量并且实例化, 这一步是为了准备 用户去调用而提前去实例化对象
  1. 其次去创建一个方法, 返回上面已经 实例化好的一个对象 , 当外界需要访问的时候, 就通过 getgetInstance() 直接 返回该对象
  1. 最后把构造方法限定为 private不允许用户自己再去实例化对象 , 告诉用户这里只能用已经 实例化的唯一的对象 来使用。

2. 懒汉模式

在这里插入图片描述

<1>. 代码演示

class  SingletonLazy {

//    声明一个成员变量
    private static  SingletonLazy singletonLazy;

    public static SingletonLazy getSingletonLazy() {
//        如果该对象没有被实例,还需要使用的话,就需要先是实例化
        if (singletonLazy == null) {
            singletonLazy = new SingletonLazy();
        }

        return singletonLazy;
    }

    // 关键一步,把构造方法私有化
    private SingletonLazy() {

    }
}


class Demo25 {

    public static void main(String[] args) {

//        接收两次
        SingletonLazy singletonLazy1 = SingletonLazy.getSingletonLazy();
        SingletonLazy singletonLazy2 = SingletonLazy.getSingletonLazy();

//        判断两次是否是相同的对象
        System.out.println(singletonLazy1 == singletonLazy2);
    
    }
}

在这里插入图片描述

<2>. 流程阐述

  • 首先声明一个 SingletonLazy 的类

  • 声明一个私有的 SingletonLazy 的 静态成员变量 , 但 不实例化

  • 当需要 访问对象 时,判断对象是否已经 实例化 , 如果 没有实例化new 出对象并返回。否则就 直接返回 即可。

  • 最终把 构造方法私有化 , 只限制在上述 if 中实例化的那个 唯一的对象

鱼式疯言

无论是 懒汉模式还是饿汉模式构造方法私有化 , 如果是通过常规方式来 再实例化对象是不太可能

如果通过 Java反射机制获取构造方法new对象 是完全有可能的, 而这种在实际生活中,是 不太可能会使用 的, 属于 非常规方式

3. 饿汉模式与懒汉模式的对比

从上面的实现思路中就可以看出两种实现方式的本质区别了,

饿汉模式: 主要为在 类加载的过程 中就 实例化对象

懒汉模式: 则是在 需要使用对象 时再 实例化对象

那么小伙伴们就可以思考一下,如果我们在实际开发中,如果追求效率的话, 是使用饿汉模式更高效, 还是懒汉模式更高效呢?

答案: 懒汉模式

其实在编程的世界中 ,“懒” 其实是一个褒义词, 越懒就越高效, 就像我们之前学习过的TCP 的 三次握手和四次挥手机制, 明明是四次握手, 但偏偏就要懒一次, 把 中间两次合并成一次来传递 , 虽然懒, 但是这样的 懒人行为正好还提高了效率

所以回到我们这里也是如此, 当需要 单例对象 时,我们才 实例化对象 , 当不需要单例对象时我们就不实例化, 并且当 第一个需要 时, 意味着后面也可能也需要 , 这样就 更好的提高效率

好比现在小编和女神在家吃饭, 一顿三餐 是需要洗碗的。

今天 女神不想洗碗 了, 就轮到小编来持家了

现在关于洗碗,小编有两种策略:

  1. 只要 一有脏碗, 我就全部洗干净 , 这就相当于 饿汉模式

  2. 脏碗都放一起,当哪一顿需要几块碗了,我们就开始洗几块碗 , 这就相当于 懒汉模式

那么我们就可以设想了,一有碗就洗 ,这样 饿汉模式 效率明显很低

但是如果是需要的时候再洗碗,需要多少洗多少 , 就相对而言懒汉模式 效果更高一点

三. 单例模式的多线程

1. 饿汉模式的多线程

其实对于 饿汉模式 来说, 无论是 串行还是并行编程 执行的代码都是大体上是一样的。

  public static Singleton getInstance() {
        return instance;
    }

小伙伴们都知道对于 串行编程和并行编程 ,最主要考虑的一点就是: 并发编程是否存在 线程安全问题 , 而区别 线程安全问题 的方式,主要是看是否存在含有 写操作

而在我们的 饿汉的单例模式 中, 上述代码中很显然 只存在读操作 , 并没有出现 赋值 这样的 写操作

所以这里的代码就和上面的代码是一样的, 小编在这里就 不赘述 了。

2. 懒汉模式的多线程

<1>. 代码演示

// 懒汉模式实现的单例模式
class SingletonLazy {
    // 此处先把这个实例的引用设为 null, 先不着急创建实例.
    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;
    }

    private SingletonLazy() { }
}

class Demo25 {
   private static SingletonLazy s1;
   private static SingletonLazy s2;
    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(()->{
            s1 = SingletonLazy.getInstance();
        });
        Thread t2 = new Thread(()->{
            s2 = SingletonLazy.getInstance();
        });

        t1.start();
        t2.start();

        Thread.sleep(1000);

        System.out.println(s1 == s2);
    }
}

在这里插入图片描述

<2>. 流程阐述

  1. 首先,声明一个类 SingletonLazy
  private static  volatile SingletonLazy instance = null;
  1. 定义一个私有的修饰的变量, 这里需要注意的一点是: 就是会产生 内存可见性问题 , 为了 防止内存可见性问题 的产生我们就需要加上 volatile 来修饰这个变量。
// 在这个条件中判定当前是否应该要加锁.

    synchronized (locker) {
        if (instance == null) {
            instance = new SingletonLazy();
        }
    }
  1. 由于在懒汉模式中,当第一次使用对象, 就涉及到 if + 赋值 的修改操作, 为了 防止线程不安全问题的发生 , 这里我们就需要把 if + 赋值 操作都打包成 原子 , 于是就使用 synchronized 进行加锁, 保证线程是安全的
// 在这个条件中判定当前是否应该要加锁.
if (instance == null) {
    synchronized (locker) {
        if (instance == null) {
            instance = new SingletonLazy();
        }
    }
}
  1. 当第2,3…次使用之后,我们不需要加锁,也就 new 对象 之后, 也就 不需要重新加锁 ,所以我们就需要在 synchronized 的外层再加 一层 if 来判断是否需要加锁

  2. 构造方法私有化(同上)

鱼式疯言

总结说明

volatile 来保证 内存可见性问题synchronized 来 解决 线程安全问题

这里的 两个if 判断方式相同 ,但是 作用是不一样 的, 外层if 判断 是否需要加锁内层if 判断 是否需要实例化对象

<3>. 加锁操作分析

   if (instance == null) {
        instance = new SingletonLazy();
    }

是的,对于上面 if + 赋值 来说, 在多线程下的并发执行下是 很容易出现线程安全问题 的。

在这里插入图片描述
如上图,如果 不加锁 的话, 就很有可能会出现 两个线程交互 , 线程一先判断, 接着切换到 另一个线程继续判断。 最终在两个线程中new 了两遍对象。

虽然这里的赋值操作, 只是 更新对象不会重复实例化对象Java垃圾回收机制也会回收原来的那个对象。 但是这里认为程序还是出现了 BUG 了。

所以我们就需要加上 将 if 和 修改操作 打包成原子 。 从而解决线程安全问题。

虽然加上了 , 但是小伙伴们有没有思考过,那么是不是每次访问对象是不是都要进行加锁操作呢?

答案: 不是

由于加锁这个操作 本身也是需要消耗性能的 ,同时我们也 不需要让每次访问对象 时, 就进行 加锁操作 , 那么我们就可以在 加锁操作的的外层 再加上 一层 if判断一旦对象被实例化了, 就不需要加锁

生活栗子:

比如我现在和女神在一起了!

我今天问女神: 你爱不爱我?

女神回答: 爱

我明天问女神: 你爱不爱我?

女神回答: 爱

如果有一天小编进去了 , 这一进去就是一年半载,不知猴年马月的

当我出来的时候, 再问女神你还爱不爱我?

这时的答案就不一定是一样的了。 所以好比 加锁 也是如此,当我们对一段代码进行加锁, 不仅会 损耗一定的性能,同时也含有 一些不确定的因素 会发生。

外层的if 的作用就在于判断 是否需要加锁

关于线程安全问题的详解文章,小伙伴们可以多回顾回顾哦~

synchronized 的线程安全问题详解文章链接

<4>. volatile 分析

对于 volatile 的使用, 主要还是提醒 编译器/ JVM 不要对 这个变量的进行优化 , 限制他们优化的程度, 读取到真正修改后的数据

在这里插入图片描述

如图线程一实例化对象了, 当线程二再次调用时, 就会 直接跳过实例化对象 的过程, 就会返回 null

内存可见性问题文章详解,小伙伴们如果忘了,记得去回忆一下哦~

内存可见性问题详解文章链接

鱼式疯言

总结概述

volatile 的作用

  • 解决 内存可见性问题
  • 解决 指令重排序的问题

总结

  • 单例模式: 初步了解了 设计模式 : 软件开发的一种特定的类似 “棋谱” 的一种开发的方案, 并且也认识了 单例模式 : 在全局范围内, 只运行访问的 一个对象的创建对象方案。

  • 单例模式的简单实现: 单例模式的简单实现: 饿汉模式与懒汉模式 。 一般而言,懒汉模式的开发效率比饿汉模式的开发效率更高

  • 单例模式的多线程:单例模式下的多线程, 唯一要熟悉的就是懒汉模式的 多线程实现 , 并且结合前面的 synchronizedvolatile 的配合使用, 解决线程安全问题。

如果觉得小编写的还不错的咱可支持 三连 下 (定有回访哦) , 不妥当的咱请评论区 指正

希望我的文章能给各位宝子们带来哪怕一点点的收获就是 小编创作 的最大 动力 💖 💖 💖

在这里插入图片描述

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

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

相关文章

android studio 配置过程

Android studio版本&#xff1a;Android Studio Ladybug | 2024.2.1 windows 10 x64 关键问题解决方法&#xff1a; 1.设置代理&#xff1a; 退出首次配置&#xff0c;进入ide&#xff08;必要时新建工程&#xff09;然后&#xff1a; 然后重启ide 等待下载完成。 代理地…

java ssm 个人学习管理系统 学习安排 学生在线学习管理 源码 jsp

一、项目简介 本项目是一套基于SSM的个人学习管理系统&#xff0c;主要针对计算机相关专业的和需要项目实战练习的Java学习者。 包含&#xff1a;项目源码、数据库脚本、软件工具等。 项目都经过严格调试&#xff0c;确保可以运行&#xff01; 二、技术实现 ​后端技术&#x…

【harbor】离线安装2.9.0-arm64架构服务制作和升级部署

harbor官网地址&#xff1a;Harbor 参考文档可以看这里&#xff1a;部署 harbor 2.10.1 arm64 - 简书。 前提环境准备&#xff1a; 安装docker 和 docker-compose 先拉arm64架构的harbor相关镜像 docker pull --platformlinux/arm64 ghcr.io/octohelm/harbor/harbor-regist…

ssm+vue708基于BS的库存管理软件设计与实现

博主介绍&#xff1a;专注于Java&#xff08;springboot ssm 等开发框架&#xff09; vue .net php phython node.js uniapp 微信小程序 等诸多技术领域和毕业项目实战、企业信息化系统建设&#xff0c;从业十五余年开发设计教学工作 ☆☆☆ 精彩专栏推荐订阅☆☆☆☆☆不…

速通LoRA:《LoRA: Low-Rank Adaptation of Large Language Models》全文解读

文章目录 总览AbstractIntroductionProblem StatementAren’t Existing Solutions Good Enough?Our MethodLow-Rank-Parametrized Update MatricesApplying LoRA to Transformer 何为高斯随机初始化Empirical ExperimentsBaselinesRoBERTa base/largeDeBERTa XXLGPT-2 medium/…

智慧园区解决方案:科技赋能,打造未来管理新典范

智慧园区作为城市发展的重要组成部分&#xff0c;正以前所未有的速度蓬勃发展。随着5G、云计算、大数据、物联网&#xff08;IoT&#xff09;、BIM&#xff08;建筑信息模型&#xff09;、人工智能&#xff08;AI&#xff09;及区块链等前沿技术的日益成熟与融合应用&#xff0…

如何优化Elasticsearch的查询性能?

优化Elasticsearch查询性能可以从以下几个方面进行&#xff1a; 合理设计索引和分片&#xff1a; 确保设置合理的分片和副本数&#xff0c;考虑数据量、节点数和集群大小。根据数据量和节点数量调整分片数量&#xff0c;避免使用过多分片&#xff0c;因为每个分片都需要额外的…

前端 JS面向对象 原型 prototype

目录 一、问题引出 二、prototype原型对象 三、小结 四、constructor 五、__proto__对象原型 六、原型链 一、问题引出 由于JS的构造函数存在内存浪费问题&#xff1a; function Star(name,age){this.namenamethis.ageagethis.singfunction () {console.log("唱歌&…

【MATLAB代码】二维平面上的TDOA,使用加权最小二乘法,不限制锚点数量,代码可复制粘贴

本文所述的MATLAB代码实现了一个基于两步加权最小二乘法的二维目标定位算法,利用多个锚点(基站)和时间差到达(TDOA)数据来估计未知目标的位置。 订阅专栏后可以看到完整代码,复制到MATLAB空脚本上面即可直接运行。若需要单独下载,可通过下面的链接:https://download.cs…

推荐一款完全开源的多端仓库管理系统

简介 模块 安装 UI展示 开源地址 今天给大家介绍一款完全开源的仓库管理系统&#xff0c;本系统采用Python语言开发。 简介 该库存管理系统是目前福特亚太区售后物流仓储供应链流程。离开福特后&#xff0c;开始了这个项目。为了帮助一些有需要的人。OneAPP 理念。支持扫…

第七部分:2. STM32之ADC实验--AD多通道(AD采集三路传感器模块实验:光敏传感器、热敏传感器、反射式传感器附赠温湿度传感器教程)

这个多通道采用非扫描模式--单次转换模式 1.代码配置链路图 2. ADC的输入通道 3.ADC的非扫描模式的转换模式&#xff08;单次和连续&#xff09; 4.ADC的扫描模式的转换模式&#xff08;单次和连续&#xff09; 5.采集校准 代码实验&#xff1a; 代码部分&#xff1a; #inclu…

手持测温热像仪市场规模:预计2030年全球市场规模将达到24.9亿美元

手持测温热像仪是一种能够实时监测和记录物体表面温度分布的高科技仪器。其核心功能在于&#xff0c;利用物体自身辐射出的热红外线来生成可视化的温度分布图像&#xff0c;进而为各种应用提供精准的温度数据。这一技术的普及和应用&#xff0c;无疑极大地提升了我们对于物体温…

web——[SUCTF 2019]EasySQL1——堆叠注入

这个题主要是讲述了堆叠注入的用法&#xff0c;来复现一下 什么是堆叠注入 堆叠注入&#xff1a;将多条SQL语句放在一起&#xff0c;并用分号;隔开。 1.查看数据库的名称 查看数据库名称 1;show databases; 发现有名称为ctftraining的数据库 2.对表进行查询 1;show tabl…

数据结构选择题及答案

一、选择题 1、下列查找方法中&#xff0c;&#xff08; &#xff09;适用于查找有序单链表。 A&#xff0e;分块查找; B&#xff0e;哈希查找; C&#xff0e;顺序查找; D&#xff0e;二分查找; 2、在有n个结点的二叉树的二叉链表表示中&#xff0c;空指针数为( )。 A&#xf…

GraphQL在现代Web开发中的应用

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 GraphQL在现代Web开发中的应用 GraphQL在现代Web开发中的应用 GraphQL在现代Web开发中的应用 引言 GraphQL 概述 定义与原理 发展…

智能社区服务小程序+ssm

智能社区服务小程序 摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步成熟。本文介绍了智能社区服务小程序的开发全过程。通过分析智能社区服务小程序管理的不足&#xff0c;创建了一个计算机管理智能社区服务小程序的方案。文…

前端上传大文件,后端报错Content-Type ‘application/octet-stream‘ is not supported【解决】

报错内容org.springframework.web.HttpMediaTypeNotSupportedException: Content-Type application/octet-stream is not supported 组件库&#xff1a;naiveui中upload进行上传功能使用的主要组件 使用原生XMLHttpRequest进行接口请求 authorization请求头需要手动写入 不…

【免越狱】iOS砸壳 可下载AppStore任意版本 旧版本IPA下载

软件介绍 下载iOS旧版应用&#xff0c;简化繁琐的抓包流程。 一键生成去更新IPA&#xff08;手机安装后&#xff0c;去除App Store的更新检测&#xff09;。 软件界面 支持系统 Windows 10/Windows 8/Windows 7&#xff08;由于使用了Fiddler库&#xff0c;因此需要.Net环境…

第三十七章 Vue之编程式导航及跳转传参

目录 一、编程式导航跳转方式 1.1. path 路径跳转 1.1.1. 使用方式 1.1.2. 完整代码 1.1.2.1. main.js 1.1.2.2. App.vue 1.1.2.3. index.js 1.1.2.4. Home.vue 1.1.2.5. Search.vue 1.2. name 命名路由跳转 1.2.1. 使用方式 1.2.2. 完整代码 1.2.2.1. main.js 1…

《硬件架构的艺术》笔记(一):亚稳态

同步系统中如果数据和时钟满足建立保持时间的要求&#xff0c;不会发生亚稳态&#xff08;meastable&#xff09;。 异步系统中数据和时钟关系不固定&#xff0c;可能违反建立保持时间&#xff0c;就会输出介于两个有效状态之间的中间级电平&#xff0c;且无法确定停留在中间状…