<JavaEE> 单例模式的两种实现:“饿汉模式”和“懒汉模式”

news2024/11/19 7:40:31

目录

一、单例模式概述

二、“饿汉模式”实现单例模式

三、“懒汉模式”实现单例模式

3.1 单线程下的“懒汉模式”

3.2 多线程下的“懒汉模式”


一、单例模式概述

1)什么是单例模式?

单例模式是一种设计模式。

单例模式可以保证某个类在程序中只存在唯一实例,即不允许创建多份实例。

使用单例模式,上述要求就得到了检查和校验。

2)单例模式的实现形式

单例模式可以通过很多种方法实现,“饿汉模式”和“懒汉模式”是其中最基础的两种,本文只介绍这两种实现。

二、“饿汉模式”实现单例模式

通过代码演示“饿汉模式”实现的单例模式:

class Singleton{
    //新建一个唯一实例;
    private static Singleton instance = new Singleton();

    //方法返回唯一实例;
    public static Singleton getInstance() {
        return instance;
    }

    //将构造方法私有化;
    private Singleton() { }
}
1)上述代码做了什么?

创建了一个被 static 修饰的实例,这个实例成为了类属性。类对象只会有一个,这个类属性也只会有一个。

私有化构造方法,外部无法 new 新的实例,只能通过 get 方法获取唯一的那一个 instance。
2)为什么叫做“饿汉模式”?

上述代码中,实例是类属性。类属性在类加载的时候就创建了,创建时机早,十分“迫切”,因此称为“饿汉模式”。

代码证明“饿汉模式”返回的实例是唯一的:

public class Singleton_Demo0 {
    public static void main(String[] args) {
        //想直接new对象,就会报错;
        //Singleton instance = new Singleton();
        
        //两次调用getInstance()方法并分别赋值;
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();

        //对比两个变量,发现是同一实例;
        if(instance1 == instance2){
            System.out.println("两个对象是同一个对象");
        }
    }
}

//运行结果:
两个对象是同一个实例
3)“饿汉模式”的单例模式在多线程下是线程安全的吗?

上述代码中,get 方法返回的是已经创建好的实例,这个操作本质上只是一个“读操作”,多个线程读取同一个变量并不会造成线程不安全。

因此“饿汉模式”的单例模式在多线程下是线程安全的。

三、“懒汉模式”实现单例模式

3.1 单线程下的“懒汉模式”

通过代码演示懒汉模式现的单例模式:

class Singleton{
    //声明一个变量作为类属性;
    private static Singleton instance = null;

    //判断变量是否为null,是则创建实例后返回,否则返回;
    public static Singleton getInstance() {
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }

    //将构造方法私有化;
    private Singleton() { }
}
1)上述代码做了什么?

声明了一个类属性。类对象只会有一个,这个类属性也只会有一个。

私有化构造方法,外部无法 new 新的实例,只能通过 get 方法获取唯一的那一个 instance。
get 方法中根据变量是否为 null 判断是否应该创建实例。
2)为什么叫做“懒汉模式”?

上述代码中,实例是在程序员第一次调用 get 方法后才创建的,创建时机较晚,或者根本不用创建,因此称为“懒汉模式”。

3.2 多线程下的“懒汉模式”

1)单线程下的“懒汉模式”在多线程下是线程安全的吗?

答案是否定的,单线程下的“懒汉模式”在多线程下是线程不安全的,我们可以从以下两个方面分析:

“原子性”:

上述代码中判断变量是否为空的代码 —— if(instance == null),和实例化代码 —— 

instance = new Singleton(),并非是“原子”的。在多线程环境下,这就可能导致线程不安全。

可以使用 synchronized 关键字,将这两句代码加锁,解决这个问题。

内存可见性和指令重排序:

因为 instance 是一个被 static 修饰的共享数据,而且编译器内部可能对实例化的代码 —— new Singleton(),进行了编译器优化。

这就无法保证内存的可见性和指令的顺序执行,因此在多线程环境下可能导致线程不安全。

可以使用 volatile 关键字,对共享数据 instance 进行修饰,解决这个问题。

使用以上两个关键字的原因和方式,详细请参考以下博客:

阅读指针 -> 《synchronized 关键字 和 volatile 关键字》<JavaEE> synchronized关键字和锁机制 -- 锁的特点、锁的使用、锁竞争和死锁、死锁的解决方法-CSDN博客文章浏览阅读70次。介绍了 synchronized 关键字 和 锁机制,其中重点介绍了锁的特点、使用方法和死锁的相关内容。https://blog.csdn.net/zzy734437202/article/details/134742168<JavaEE> volatile关键字 -- 保证内存可见性、禁止指令重排序-CSDN博客文章浏览阅读59次。简单介绍什么是内存可见性和指令重排序。volatile关键字可以将这两种编译器优化强制关闭。https://blog.csdn.net/zzy734437202/article/details/134757070

2)“懒汉模式”在多线程下应该怎么编写?

根据上述分析,根据单线程模式下的“懒汉模式”进行改进。

方法如下:

增加 volatile 关键字对共享数据进行修饰。

为判断是否为 null 和 实例化的代码加锁,使这两句代码称为“原子”。

增加 volatile 关键字对共享数据进行修饰:

private volatile static Singleton instance = null;

为判断是否为 null 和 实例化的代码加锁,使这两句代码称为“原子”:

    public static Singleton getInstance() {
        synchronized (locker){
            if(instance == null){
                instance = new Singleton();
            }
        }
        return instance;
    }
3)“双重校验锁”

我们再仔细分析一下上述的 get 方法。

假设程序需要多次调用这个 get 方法,那么每一次进入都会进行加锁,加锁是会增加系统开销的。

那么是否真的有必要每次都加锁呢?

当 get 方法被第一次调用,实例就会被创建,那么后续再调用这个 get 方法时,返回实例就好了,加锁部分的代码块,完全可以不用执行。

在加锁的代码块之外,再增加一个if(instance == null)进行判断,那么实例在被创建之后,也就不会再进入加锁的代码块中了。

我们成功利用“双重校验锁”,优化了程序。

代码演示“双重校验锁”优化后的 get 方法:

    public static Singleton getInstance() {
        //这个if用于判断是否需要加锁;
        if(instance == null){
            synchronized (locker){
                //这个if用于判断是否需要新建实例;
                if(instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }

经过以上的完善和优化,我们终于可以写出在多线程下保证线程安全的“懒汉模式”单例模式了:

class Singleton{
    //声明一个变量作为类属性;
    private volatile static Singleton instance = null;

    private static final Object locker = new Object();

    //判断变量是否为null,是则创建实例后返回,否则返回;
    public static Singleton getInstance() {
        //这个if用于判断是否需要加锁;
        if(instance == null){
            synchronized (locker){
                //这个if用于判断是否需要新建实例;
                if(instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }

    //将构造方法私有化;
    private Singleton() { }
}

阅读指针 -> 《阻塞队列》

链接生成中.........

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

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

相关文章

【C++】STL简介(了解)【STL的概念,STL的历史缘由,STL六大组件、STL的重要性、以及如何学习STL、STL的缺陷的讲解】

这里写自定义目录标题 一、什么是STL二、STL的版本1. 原始版本2. P. J. 版本3. RW版本★ 4. SGI版本 三、STL的六大组件四、STL的重要性五、如何学习STL六、STL的缺陷 一、什么是STL STL ( standard template libaray - 标准模板库 ):是C标准库 的重要组成部分&…

Spring到底是如何解决循环依赖问题的?

Spring作为当前使用最广泛的框架之一,其重要性不言而喻。所以充分理解Spring的底层实现原理对于咱们Java程序员来说至关重要,那么今天笔者就详细说说Spring框架中一个核心技术点:如何解决循环依赖问题? 什么是循环依赖问题&#x…

如何保持操纵机构丝杆的精度?

滚珠丝杆是操纵机构中的重要组成部分,可以传递较高的扭矩,并且具有低摩擦、高效率和快速响应的特性,这使得操纵机构能够实现高速、高精度的运动控制,这对于整个系统的性能和精度具有决定性的影响,保持操纵机构丝杆的精…

U-Shape Transformer for Underwater Image Enhancement(用于水下图像增强的U型Transformer)总结

背景 现有的水下数据集或多或少存在图像数量少、水下场景少、甚至不是真实场景等缺点,限制了数据驱动的水下图像增强方法的性能。此外,水下图像在不同颜色通道和空间区域的衰减不一致也没有统一的框架。 贡献 1)提出了一种处理 UIE 任务的…

UI咨询公司-蓝蓝设计:顶级秘籍:提升UI设计吸引力的3大绝招

想要让你的UI设计在海量应用中脱颖而出,吸引用户眼球吗?如果你正在寻找提升UI设计吸引力的绝妙方法,那么你绝对不能错过本文!我们将为你揭示顶级UI设计师都不会告诉你的3大绝招,让你轻松掌握提升UI设计吸引力的关键技巧…

Cysteine Protease inhibitor 921625-62-9科研

Cysteine Protease inhibitor 5-氨基-3-苯基-1,2,4-噻二唑 英文名称:Cysteine Protease inhibitor 中文名称:半胱氨酸蛋白酶抑制剂 化学名称:5-氨基-3-苯基-1,2,4-噻二唑 CAS:921625-62-9 外观:固体粉末 分子式&#…

智能联动第三方告警中心,完美实现故障响应全闭环

前言 我们曾讨论完善的告警策略是整个数据监控系统的重要组成部分(参见《机智的告警策略,完善监控系统的重要一环》),介绍了如何配置告警通知以及场景示例,帮助用户及时更多潜在的故障和问题,有效地保障系…

值班日历实现不同人显示不同的颜色区别

前端UI用的移动端的vantUI。这里只是我的思路总结&#xff0c;和用什么UI框架关系不大。 先看效果图&#xff1a; <van-calendarref"calendar":poppable"false":show-confirm"false":style"{ height: 580px }":min-date"minD…

11.一维字符数组——求字符串长度, 占内存字节数

文章目录 前言一、题目描述 二、题目分析 三、解题 程序运行代码 前言 本系列为一维字符数组编程题&#xff0c;点滴成长&#xff0c;一起逆袭。 一、题目描述 求字符串长度&#xff0c; 占内存字节数 二、题目分析 求字符串长度 法一&#xff1a; while(str[i]!‘\0’){ i…

webview2 runtime 安装错误 0x800700b7

最简单的一种是删除C:\Program Files (x86)\Microsoft文件夹&#xff0c;一般情况下&#xff0c;这个文件夹是空的&#xff0c;但在系统目录下&#xff0c;不允许删除&#xff0c;可以选中获取下管理员权限&#xff0c;或是用第三方软件&#xff0c;直接强制删除。 删除之后就…

分享88个焦点幻灯JS特效,总有一款适合您

分享88个焦点幻灯JS特效&#xff0c;总有一款适合您 88个焦点幻灯JS特效下载链接&#xff1a;https://pan.baidu.com/s/1geIPx77y5OCJvLaECq9upQ?pwd6666 提取码&#xff1a;6666 Python采集代码下载链接&#xff1a;采集代码.zip - 蓝奏云 学习知识费力气&#xff0c;…

【JavaEE】单例模式

作者主页&#xff1a;paper jie_博客 本文作者&#xff1a;大家好&#xff0c;我是paper jie&#xff0c;感谢你阅读本文&#xff0c;欢迎一建三连哦。 本文于《JavaEE》专栏&#xff0c;本专栏是针对于大学生&#xff0c;编程小白精心打造的。笔者用重金(时间和精力)打造&…

Java基本数据类型、包装类及拆装箱详解

Java的基本数据类型和对应的包装类是Java语言中处理数据的两个关键概念。基本数据类型提供了简单而高效的方式来存储数据&#xff0c;而包装类使得基本数据类型具有对象的特性。本文将深入探讨基本数据类型与包装类的应用场景及详细描述&#xff0c;并对自动拆箱和装箱的源码实…

【信息安全】MD5哈希函数

1. MD5介绍 MD5&#xff08;Message Digest Algorithm 5&#xff09;是一种常见的哈希函数&#xff0c;通常用于产生数据的数字摘要&#xff0c;也称为哈希值或摘要值。它是由Ron Rivest在1991年设计的&#xff0c;广泛用于数据完整性验证、密码存储、数字签名等领域。 MD5哈…

HTML CSS JavaScript的网页设计

一、网页界面效果&#xff1a; 二、HTML代码&#xff1a; <!DOCTYPE html> <!-- 声明文档类型--> <html lang"en"> …

如何定位当生产环境CPU飙升的时候的问题

其他系列文章导航 Java基础合集数据结构与算法合集 设计模式合集 多线程合集 分布式合集 ES合集 文章目录 其他系列文章导航 文章目录 前言 一、排查思路 二、预防CPU飙升 三、总结 前言 在当今的信息化时代&#xff0c;计算机系统在各行各业都发挥着重要的作用。然而&a…

Java中实用的策略模式【Strategy】

一、简介 我们知道Java中有许多的设计模式&#xff0c;总共32个左右。常见的比如简单工厂、建造者、原型、代理、桥接等&#xff0c;这些设计模式相当于是一个规范&#xff0c;主要是总结出来便于大家理解开发的一种算法思路。 今天主要是给大家介绍一下我们常见的策略模式&a…

自动化框架错误排查:本地全通过,pipline上大部分报错

现象: 最近经过一次切环境和验证码部分的代码重构,果不其然,我们的自动化框架就出错了 我在本地修改调试,并在堡垒机上全部跑过 但在pipline上则大部分报错 进一步排查 这么多case报错,而且报错log都一模一样,推断是底层出错 我在堡垒机上使用命令行来跑case,发现与…

【深度学习】Stable Diffusion中的Hires. fix是什么?Hires. fix原理

文章目录 **Hires. fix****Extra noise**Upscalers Hires. fix https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Features#hires-fix 提供了一个方便的选项&#xff0c;可以部分地以较低分辨率呈现图像&#xff0c;然后将其放大&#xff0c;最后在高分辨率下添…

day69

今日回顾 Django与Ajax 一、什么是Ajax AJAX&#xff08;Asynchronous Javascript And XML&#xff09;翻译成中文就是“异步Javascript和XML”。即使用Javascript语言与服务器进行异步交互&#xff0c;传输的数据为XML&#xff08;当然&#xff0c;传输的数据不只是XML,现在…