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

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

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

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

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

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

在这里插入图片描述

引言

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

目录

  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 的配合使用, 解决线程安全问题。

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

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

在这里插入图片描述

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

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

相关文章

CTF记录

1. [SWPUCTF 2022 新生赛]android 用jadx打开&#xff0c;然后搜索NSS关键字 NSSCTF{a_simple_Android} 2. [SWPU 2024 新生引导]ez_SSTI 模板注入题目&#xff0c;直接焚靖可以秒了 填入数据 ls / 然后 cat /flag即可 获取成功 NSSCTF{2111e7ad-97c5-40d5-9a3b-a2f657bd45e8…

【C++滑动窗口 】2831. 找出最长等值子数组|1975

本文涉及的基础知识点 C算法&#xff1a;滑动窗口总结 本题其它解法 【C二分查找 滑动窗口】2831. 找出最长等值子数组|1975 LeetCode2831. 找出最长等值子数组 给你一个下标从 0 开始的整数数组 nums 和一个整数 k 。 如果子数组中所有元素都相等&#xff0c;则认为子数组…

《JVM第9课》垃圾回收器

先来看一张图&#xff0c;串行代表两个垃圾回收器按顺序执行&#xff0c;并行代表同时执行。STW代表工作线程暂停&#xff0c;Stop The World的意思。 垃圾回收器执行顺序执行方式作用区域使用算法说明Serial GC串行工作线程暂停&#xff0c;单线程进行垃圾回收新生代复制算法…

gitlab项目如何修改主分支main为master,以及可能遇到的问题

如果你希望将 Git 仓库的主分支名称从 main 修改为 master&#xff1a; 1. 本地修改分支名称 首先&#xff0c;切换到 main 分支&#xff1a; git checkout main将 main 分支重命名为 master&#xff1a; git branch -m main master2. 更新远程仓库 将本地更改推送到远程仓库…

【Keil5 使用Debug调试,阻塞在System_Init()中,并报错显示:no ‘read‘ permis】

计算机疑难杂症记录与分享006 Keil5 使用Debug调试&#xff0c;阻塞在System_Init()中&#xff0c;并报错显示error 65: access violation at 0x40021000 : no read permission1、问题背景2、问题原因3、问题解决3.1、解决方法1(亲测有效)&#xff1a;3.1.1、修改后的现象13.1.…

接口自动化测试实战(全网唯一)

&#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 接口自动化测试是指通过编写程序来模拟用户的行为&#xff0c;对接口进行自动化测试。Python是一种流行的编程语言&#xff0c;它在接口自动化测试中得到了广…

ubuntu 20.04 NVIDIA驱动、cuda、cuDNN安装

1. NVIDIA驱动 系统设置->软件和更新->附加驱动->选择NVIDIA驱动->应用更改。该界面会自动根据电脑上的GPU显示推荐的NVIDIA显卡驱动。 运行nvidia-smi: NVIDIA-SMI has failed because it couldnt communicate with the NVIDIA driver. Make sure that the lat…

HarmonyOS开发 API 13发布首个Beta版本,解决了哪些问题?

HarmonyOS 5.0.1 Beta3&#xff0c;是HarmonyOS开发套件基于API 13正式发布的首个Beta版本。该版本在OS能力上主要增强了C API的相关能力&#xff0c;多个特性补充了C API供开发者使用。HarmonyOS 5.0.1 Beta3完整配套信息如下&#xff1a; 已解决的问题 DevEco Studio 5.0.…

SQL,力扣题目1194,锦标赛优胜者

一、力扣链接 LeetCode1194 二、题目描述 Players 玩家表 -------------------- | Column Name | Type | -------------------- | player_id | int | | group_id | int | -------------------- player_id 是此表的主键(具有唯一值的列)。 此表的每一行表示每个玩…

计算机毕业设计Python+图神经网络考研院校推荐系统 考研分数线预测 考研推荐系统 考研爬虫 考研大数据 Hadoop 大数据毕设 机器学习 深度学习

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

virtualBox部署minikube+istio

环境准备 virtualBox安装 直接官网下载后安装即可&#xff0c;网上也有详细教程。镜像使用的centos7。 链接&#xff08;不保证还可用&#xff09;&#xff1a;http://big.dxiazaicc.com/bigfile/100/virtualbox_v6.1.26_downcc.com.zip?auth_key1730185635-pWBtV8LynsxPD0-0-…

一文了解Android本地广播

在 Android 开发中&#xff0c;本地广播&#xff08;Local Broadcast&#xff09;是一种轻量级的通信机制&#xff0c;主要用于在同一应用进程内的不同组件之间传递消息&#xff0c;而无需通过系统的全局广播机制。这种方法既可以提高安全性&#xff08;因为广播仅在应用内传播…

CoD-MIL: 基于诊断链提示的多实例学习用于全切片图像分类|文献速递-基于深度学习的病灶分割与数据超分辨率

Title 题目 CoD-MIL: Chain-of-Diagnosis Prompting Multiple Instance Learning for Whole Slide Image Classification CoD-MIL: 基于诊断链提示的多实例学习用于全切片图像分类 01 文献速递介绍 病理检查被广泛视为肿瘤诊断的金标准&#xff0c;因为它为治疗决策和患者…

Socket 和 WebSocket 的应用

Socket&#xff08;套接字&#xff09;是计算机网络中的一个抽象层&#xff0c;它允许应用程序通过网络进行通信。套接字用于跨网络的不同主机上的应用程序之间的数据交换。在互联网中&#xff0c;套接字通常基于 TCP&#xff08;传输控制协议&#xff09;或 UDP&#xff08;用…

uniapp发布到微信小程序,提示接口未配置在app.json文件中

使用uniapp打包上传微信小程序发布&#xff0c;在提交审核时提示 “接口未配置在app.json文件中” 如下图所示 解决方法&#xff1a;在manifest.json文件中打开源码视图&#xff0c;添加 requiredPrivateInfos 字段键入所需要的接口&#xff08;数组&#xff09;

Golang | Leetcode Golang题解之第557题反转字符串中的单词III

题目&#xff1a; 题解&#xff1a; func reverseWords(s string) string {length : len(s)ret : []byte{}for i : 0; i < length; {start : ifor i < length && s[i] ! {i}for p : start; p < i; p {ret append(ret, s[start i - 1 - p])}for i < le…

[产品管理-58]:安索夫矩阵矩阵帮助创业者确定研发出来的产品在市场中定位策略

目录 一、提出背景 二、核心思想与结构 三、应用背景与领域 四、实践案例 安索夫矩阵&#xff08;Ansoff Matrix&#xff09;&#xff0c;也被称为产品/市场方格或成长矢量矩阵&#xff0c;其应用背景可以从以下几个方面进行详细阐述&#xff1a; 一、提出背景 安索夫矩阵…

使用 Vue 配合豆包MarsCode 实现“小恐龙酷跑“小游戏

作者&#xff1a;BLACK595 “小恐龙酷跑”&#xff0c;它是一款有趣的离线游戏&#xff0c;是Google给Chrome浏览器加的一个有趣的彩蛋。当我们浏览器断网时一只像素小恐龙便会出来提示断网。许多人认为这只是一个可爱的小图标&#xff0c; 但当我们按下空格后&#xff0c;小恐…

运行ts文件出错及解决办法

运行ts文件出错及解决办法 TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension “.ts” 这个错误是因为 ts-node 无法直接处理 TypeScript 文件作为 ES 模块。你可以尝试以下解决方案&#xff1a; 解决方案 1: 使用 --loader ts-node/esm 选项 如果你使用的是 …