多线程 (六) 单例模式

🎉🎉🎉点进来你就是我的人了
博主主页:🙈🙈🙈戳一戳,欢迎大佬指点!

人生格言:当你的才华撑不起你的野心的时候,你就应该静下心来学习!

欢迎志同道合的朋友一起加油喔🦾🦾🦾
目标梦想:进大厂,立志成为一个牛掰的Java程序猿,虽然现在还是一个🐒嘿嘿
谢谢你这么帅气美丽还给我点赞!比个心


目录

一. 什么是单例模式

二. 单例模式的类型

三. 单例模式优缺点

四. 创建单例模式

1.饿汉式实现单例模式

2. 懒汉式实现单例模式

3. 两种实现方式的比较

五.解决懒汉单例的实现带来的线程不安全问题



一. 什么是单例模式

单例模式是指在内存中只会创建且仅创建一次对象的设计模式。在程序中多次使用同一个对象且作用相同时,为了防止频繁地创建对象使得内存飙升,单例模式可以让程序仅在内存中创建一个对象,让所有需要调用的地方都共享这一单例对象。

二. 单例模式的类型

单例模式有两种类型:

  • 懒汉式:在真正需要使用对象时才去创建该单例类对象
  • 饿汉式:在类加载时已经创建好该单例对象,等待被程序使用

三. 单例模式优缺点

优点: 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。

2、避免对资源的多重占用(比如写文件操作)。

缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

主要解决:一个全局使用的类频繁地创建与销毁。

何时使用:当您想控制实例数目,节省系统资源的时候。

如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。

关键代码:构造函数是私有的。

注意:

1、单例类只能有一个实例。

2、单例类必须自己创建自己的唯一实例。

3、单例类必须给所有其他对象提供这一实例。

四. 创建单例模式

1.饿汉式实现单例模式

主要实现步骤:(饿汉主要体现在直接创建对象)

1.先将构造方法私有化, 避免用户直接在外部 new 新对象。

2.在类的内部属性部分直接创建一个对象(我们已经将构造方法私有化了,因此在外部没有办法 new 新对象,所以只能在类的内部创建对象)。

3.向外提供一个公共的静态方法,用于外部引用该对象。(创建好了对象后,如果对外不提供公共方法,我们就没有办法使用这个被创建的对象。)

注意:之所以用static是因为类外无法直接创建对象,我们加上static就可以通过类名获取该类的属性和方法了.

代码演示:

public class Singleton {

    // 提供一个对象
    private static Singleton instance = new Singleton();

    // 防止外部new对象
    private Singleton(){}

    // 使得外部能够访问到创建的对象
    public static Singleton getInstance(){
        return instance;
    }
}

2. 懒汉式实现单例模式

主要实现步骤:

1.先将构造器私有化, 避免用户直接在外部 new 新对象。

2.在类的内部 声明一个对象引用,但是不创建, 即先不 new;

3.向外提供一个公共的静态方法,用于创建对象并返回对象引用。 

(懒汉主要体现在不直接创建对象,而是在需要的时候再创建)

代码演示:

public class Singleton {

    // 提供一个对象的引用
    private static Singleton instance = null;

    // 防止外部new对象
    private Singleton(){}

    // 用于创建对象或使得外部能够访问到创建的对象
    public static Singleton getInstance(){
        if (instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}

3. 两种实现方式的比较

两种实现方法都是通过将构造方法限定为private避免了类在外部被实例化,在同一个虚拟机范围内,Singleton的唯一实例只能通过getInstance()方法访问。

(事实上,通过Java反射机制是能够实例化构造方法为private的类的,那基本上会使所有的Java单例实现失效。此问题在此处不做讨论,姑且掩耳盗铃地认为反射机制不存在。)

但是以上懒汉式单例的实现没有考虑线程安全问题,它是线程不安全的并发环境下很可能出现多个Singleton实例,相比较饿汉式单例的实现仅仅只是读取了变量的内容,因此,是线程安全的!

五.解决懒汉单例的实现带来的线程不安全问题

       

 在懒汉模式的这段代码中,既包含了读,又包含了写,且读写还不是原子的(分成了两个步骤~), 所以存在线程安全问题。

举例解释说明:

比如:两个线程t1,t2同时进入该方法,需要返回给 t1 ,t2 对象实例。当 t1 进入该方法后,进行判断,不为 null,因此需要创建一个对象,而在创建对象的过程中,t2 刚好也进入了该方法,也被判断成了不为 null,此时,t1, t2就指向不同的对象了,所以它是线程不安全的。

对于原子性所引起的线程安全问题,前面我们有讲线程安全及解决方案(看这一篇就够了)

这里我们可以对这段代码直接加锁,这样就可以保证在进行读和写操作的时候,只有一个线程在操作,也就可以避免另一个线程同时进入 if 语句去创建对象了

 public static Singleton getInstance(){
        //对类对象加锁
        synchronized (Singleton.class) {
            if (instance == null) {
                instance = new Singleton();
            }
            return instance;
        }
    }

问题解决了,可是,这种方案真的好吗?

对于加锁操作来说,加锁确实可以让代码保证线程安全,但是也会付出相应的代价, 最典型的代价就是程序运行变慢了。本来我多个线程运行的好好的,你给我加了把锁,其他线程不就进不去了吗?(一定程度上的阻塞)

此时我们可以再加一个判断!判断 instance 是否被初始化完成,如果已经初始化,就不必进入加锁的代码块了

// 用于创建对象或使得外部能够访问到创建的对象
    public static Singleton getInstance(){
        //用于判断是否加锁
        if(instance == null) {
            synchronized (Singleton.class) {
                //用于判断是否创建对象实例
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }

现在代码似乎看起来已经没有问题了,但实际上还有可能发生内存可见性问题!

如果多个线程,都去调用 getInstance方法,就会造成大量的读 instance 操作。而,我们知道,对于在内存读和在寄存器读来说,后者的效率更高。因此,编译器会自作主张,对代码进行优化:将读内存操作优化成读寄存器操作。
也就是说,即使某个线程将instance初始化了,但是由于编译器的优化,在其他线程的视角看起来,instance 还是null。

也就是说,这里的内存可见性问题,会导致第一个if语句失效,即不该加锁的时候还会加锁。因此,我们需要保证内存可见!即使用 volatile 对 instance 进行修饰,被volatile修饰的变量,都会从主内存中读取该变量。同时,使用 volatile 可以防止 指令重排序

 至此,懒汉单例的实现带来的线程不安全问题基本得以解决!

对于线程安全问题不是太了解的可以参考我上一篇博客

线程安全及解决方案(看这一篇就够了)

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

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

相关文章

蓝桥杯刷题冲刺 | 倒计时19天

作者:指针不指南吗 专栏:蓝桥杯倒计时冲刺 🐾马上就要蓝桥杯了,最后的这几天尤为重要,不可懈怠哦🐾 文章目录1.抓住那头牛2.排列序数1.抓住那头牛 题目 链接: 抓住那头牛 - C语言网 (dotcpp.com…

网络安全之防火墙

目录 网络安全之防火墙 路由交换终归结底是联通新设备 防御对象: 定义: 防火墙的区域划分: 包过滤防火墙 --- 访问控制列表技术 --- 三层技术 代理防火墙 --- 中间人技术 --- 应用层 状态防火墙 --- 会话追踪技术 --- 三层、四层 UTM …

CrossOver零知识学习1 —— 初识

本文部分内容参考CrossOver22全新版功能简介 免费mac虚拟机工具_CoCo玛奇朵的博客-CSDN博客 特此致谢! 一、CrossOver简介 CrossOver是由CODE WEAVERS公司开发的类虚拟机软件,目的是使Linux和Mac OS X操作系统和Window系统兼容。CrossOver英文原意为“…

强烈推荐:0基础入门网安必备《网络安全知识图谱》

蚁景网安学院一直专注于网安实战技能培养,提供全方位的网安安全学习解决方案。我们集聚专业网安技术大佬资源,倾力打造了这本更全面更系统的“网络安全知识图谱”,让大家在网络安全学习路上不迷茫。 在这份网安技能地图册里,我们对…

01 | Msyql系统架构

目录MySQL系统架构连接器查询缓存分析器优化器执行器MySQL系统架构 大体来说,MySQL分为Server层和引擎层两部分。 Server层包含链接器、查询缓存、分析器、优化器和执行器,而引擎层负责的是数据的存储和读取,支持InnoDB、Myisam、Memory等多…

CSS实现文字凹凸效果

使用两个div分别用来实现凹凸效果;text-shadow语法 text-shadow: h-shadow v-shadow blur color; h-shadow:必需。水平阴影的位置。允许负值。 v-shadow :必需。垂直阴影的位置。允许负值。 blur:可选,模糊的距离。 co…

【C语言】你真的了解结构体吗

引言✨我们知道C语言中存在着整形(int、short...),字符型(char),浮点型(float、double)等等内置类型,但是有时候,这些内置类型并不能解决我们的需求,因为我们无法用这些单一的内置类型来描述一些复杂的对象&#xff0c…

k8s部署prometheus

k8s部署prometheus 版本说明: k8s:1.24.4 prometheus:release-0.12(https://github.com/prometheus-operator/kube-prometheus.git) 本次部署采用operator的方式将prometheus部署到k8s中,需对k8s和prom…

springboot+vue驾校管理系统 idea科目一四预约考试,练车

加大了对从事道路运输经营活动驾驶员的培训管理力度,但在实际的管理过程中,仍然存在以下问题:(1)管理部门内部人员在实际管理过程中存在人情管理,不进行培训、考试直接进行发证。(2)从业驾驶员培训机构不能严格执行管理部门的大纲…

SpringBoot解析指定Yaml配置文件

再来个文章目录 文章目录前言1、自定义配置文件2、配置对象类3、YamlPropertiesSourceFactory下面还有投票,帮忙投个票👍 前言 最近在看某个开源项目代码并准备参与其中,代码过了一遍后发现多个自定义的配置文件用来装载业务配置代替数据库…

使用 Python 从点云生成 3D 网格

从点云生成 3D 网格的最快方法 已经用 Python 编写了几个实现来从点云中获取网格。它们中的大多数的问题在于它们意味着设置许多难以调整的参数,尤其是在不是 3D 数据处理专家的情况下。在这个简短的指南中,我想展示从点云生成网格的最快和最简单的过程。…

继承和派生

🐶博主主页:ᰔᩚ. 一怀明月ꦿ ❤️‍🔥专栏系列:线性代数,C初学者入门训练,题解C,C的使用文章,「初学」C 🔥座右铭:“不要等到什么都没有了,才下…

手撕数据结构—队列

队列队列的话只允许在一端插入,在另外一端删除。插入数据的那一段叫做队尾,出数据的那一段叫做队头(从尾巴插入)。因此的话队列是先进先出的。入的顺序与出的顺序的话是一样的。这个与栈是不一样的,因为栈的话就是说如…

问题【Java 基础】

基础1、成员变量与局部变量的区别2、静态变量有什么作用3、字符型常量和字符串常量的区别4、静态方法为什么不能调用非静态成员5、静态方法和实例方法有何不同6、重载和重写有什么区别7、什么是可变长参数8、Java 中的几种基本数据类型了解么9、基本类型和包装类型的区别10、包…

【数据结构】树和二叉树的概念及结构

目录 1.树概念及结构 1.1 树的概念 1.2 树的相关概念 1.3树的表示 1.4 树在实际中的应用 2.二叉树概念及结构 2.1 概念 2.2 特殊的二叉树 2.2.1 满二叉树 2.2.2 完全二叉树 1.树概念及结构 1.1 树的概念 树是一种非线性的数据结构,它是由n(n>0) 个有…

一款专门为自动化测试打造的集成开发工具【Aqua】,“能快速构建自动化测试项目”,就问你爽不爽吧,,,

你好,我是不二。 随着行业内卷越来越严重,自动化测试已成为测试工程师的必备技能,谈及自动化测试肯定少不了编程,说到编程肯定离不开集成开发工具,比如:IntelliJ IDEA可以帮助我们快速构建Maven项目、sprin…

前端已死?后端已亡?弯弯绕绕,几分真几分假

前段时间,我在掘金分享了一篇GPT-4 性能文章,也许是过于强大带来的威胁性,引来评论区的排队哀嚎(如下图),所以“前端已死,后端已亡”这个概念真的成立吗?本文着重探讨前端。 前端和后…

警惕,3月20日WOS目录更新,50本SCI/SSCI被剔除,这个出版社多达18本

2023年3月SCI、SSCI期刊目录更新 2023年3月20日,Web of Science核心期刊目录再次更新!此次2023年3月SCIE & SSCI期刊目录更新,与上次更新(2023年2月)相比,共有50本期刊被剔除出SCIE & SSCI期刊目录…

[ 网络 ] 应用层协议 —— HTTP协议

目录 1.HTTP协议 1.1URL urlencode和urldecode 2. HTTP协议格式 HTTP请求 HTTP响应 3.告知服务器意图的HTTP方法 GET:获取资源 POST:传输实体主体 GET和POST的区别 使用Cookie的状态管理 4.返回结果的HTTP状态码 状态码告知从服务器端返回的…

三月份跳槽了,历经字节测开岗4轮面试,不出意外,被刷了...

大多数情况下,测试员的个人技能成长速度,远远大于公司规模或业务的成长速度。所以,跳槽成为了这个行业里最常见的一个词汇。 前几天,我看到有朋友留言说,他在面试字节的测试开发工程师的时候,灵魂拷问三小…
最新文章