单例模式之饿汉模式懒汉模式

news2024/10/2 12:32:15

前言

单例模式能保证某个类在程序中只存在唯一一份实例,而不会创建出多个实例,比如 JDBC 中的 DataSource 实例就只需要一个。单例模式具体的实现方式有"饿汉" 和 "懒汉" 两种。

1.饿汉模式(类加载的同时创建实例)

class Singleton {
    // 先创建出示例
    private static Singleton instance = new Singleton();

    // 如果需要使用这个唯一实例, 统一通过 Singleton.getInstance() 方式来获取.
    public static Singleton getInstance() {
        return instance;
    }

    // 把构造方法设为 private. 在类外面, 就无法通过 new 的方式来创建这个 Singleton 实例
    private Singleton() {}
}

此时饿汉模式即使在多线程情况下,也是线程安全的,因为只涉及到读操作。

2.懒汉模式

2.1单线程版的懒汉模式

class SingletonLazy {
    private static SingletonLazy instance = null;

    public static SingletonLazy getInstance() {
                if (instance == null) {
                    instance = new SingletonLazy();
                }
        return instance;
    }

    private SingletonLazy() {}
}

如果此时这种懒汉模式放在多线程中是不安全的,因为既涉及到了读操作也涉及到了写操作。如果此时多个线程同时都读到了instance == null,那么就会多次进行new操作,这显然就不是单例了。

2.2进行加锁,对单线程版懒汉模式进行改进

class SingletonLazy {
    private static SingletonLazy instance = null;

    public static SingletonLazy getInstance() {
        synchronized (SingletonLazy.class){
            if (instance == null) {
                instance = new SingletonLazy();
            }
        }
        return instance;
    }

    private SingletonLazy() {}
}

这样进行加锁之后,就保证了读操作和修改操作是一个整体了。但是,目前进行加锁改进后的懒汉模式代码仍然还有问题,这样的加锁之后,就意味着每一次的getInstance都需要加锁,因为进行加锁操作是需要花费很大开销的,那么每一次都需要进行加锁,会浪费很大的开销。这里的加锁的本意是只在new出对象之前加,是有必要的,一旦new对象完成,后续加锁也就没有意义了,因为instance的值一定是非空的,直接就走return了。所以,还有改进的空间。

2.3继续改进(满足特定条件才进行加锁,也就是第一次new对象的时候才进行加锁)

class SingletonLazy {
    private static SingletonLazy instance = null;

    public static SingletonLazy getInstance() {
        if (instance == null){
            synchronized (SingletonLazy.class){
                if (instance == null) {
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }

    private SingletonLazy() {}
}

第一层if (instance == null)判断是为了判断是否是第一次new对象,是否需要进行加锁;synchronized (SingletonLazy.class)此处就是在满足了特定条件后才进行加锁,即第一次new对象的时候;第二层 if (instance == null)是判断是否要创建对象。加锁操作可能会引起线程阻塞,当执行到锁结束之后,执行到第二个if 的时候第二个if和第一个if之间可能已经隔了很久的时间,程序的运行内部的状态,这些变量的值,都可能已经发生很大改变了,比如第一次都是null,有很多个线程都进入了第一个if,但是此时在synchronized这里阻塞等待,只有一个能进去,当后面的线程再次进去的时候,一定要靠第二个if来判断,因为此时instance很可能已经被第一次进去的线程给改了,所以很可能不为空,所以还需要第二个if来判断

但是此时,这段代码还有一些问题,那就是内存可见性问题。比如,同时有很多很多的线程都去getInstance,此时只有一个线程是真正进入了第二个if里面读取了内存,其他在外面的很多线程都是读寄存器/cache,那么此时编译器很可能会认为多次读取到的都是null,有可能会被优化处理。

还有就是在这个过程中,会涉及到指令重排序的问题。此处的instance = new SingletonLazy();可以拆分成三个步骤,(1)申请内存空间,(2)调用构造方法,把这个内存空间初始化成一个合理的对象,(3)把内存空间的地址赋值给instance引用。

在正常情况下,是按照123这个顺序来执行的,但是编译器为了提高程序效率,可能会调整代码的执行顺序,比如把123变成132。

假设t1是按照132的步骤来执行的,当t1执行到13步骤结束,2步骤之前的时候,被切出CPU了,换t2来执行。此时,t1执行完步骤3之后,在t2看来,此处的引用就非空了,此时此刻,t2就直接return了instance引用,并且可能会尝试使用引用中的属性。但是由于t1中的步骤2还没有完成,t2拿到的是非法的对象,还没有构造完成的不完整的对象。

2.4使用volatile关键字来解决内存可见性和禁止指令重排序的问题

class SingletonLazy {
    private volatile static SingletonLazy instance = null;

    public static SingletonLazy getInstance() {
        if (instance == null){
            synchronized (SingletonLazy.class){
                if (instance == null) {
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }

    private SingletonLazy() {}
}

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

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

相关文章

SOLIDWORKS装配体如何快速导出BOM丨慧德敏学

BOM作为产品数据的组成部分,它的重要性不言而喻。采购需要BOM、成本核算需要BOM、领料加工和装配需要BOM、录入ERP需要BOM……可以说,BOM与图纸同样重要,有些产品,可以没有图纸,但是不能没有BOM。借助SOLIDWORKS BOM插…

带你打开C语言的大门

最近有刚开始学习编程的同学问我:“C语言是什么?C语言是怎么来的?C语言用来干什么?”。对,在学习C语言之前,首先了解C语言的发展例程,这应该是每一个刚刚开始学习C语言的人应该了解的&#xff0…

什么是"文件表项"

从Linux的层次角度来说,在用户空间是存在这样的概念的,这个概念是存在内核空间的,而且是针对打开的文件的! 内核用三种数据结构来描述一个打开的文件。 数据结构一: 文件描述符表(descriptor table): 每个进程都有它独立的描述符表…

【代码调试】《FSCE: Few-Shot Object Detection via Contrastive Proposal Encoding》

论文地址:https://arxiv.org/pdf/2103.05950.pdf 代码地址:https://github.com/megvii-research/FSCE 论文阅读:https://blog.csdn.net/qiankendeNMY/article/details/128390284 我的配置: Python :3.8(ubuntu18.04) …

小程序发布体验版流程

一、微信开发者工具操作 1. 点击 工具 -> 上传(或 直接点击右上角“上传”按钮) 【注意】 如果使用的测试 appid 则【上传】按钮不能点击,必须使用真实 appid 2. 如果之前有发布过体验版,会提示继续操作将会覆盖之前的体验版…

MyBatisPlus ---- 基本CRUD

MyBatisPlus ---- 基本CRUD1. BaseMapper2. 插入3. 删除a>通过id删除记录b>通过id批量删除记录c>通过map条件删除记录4. 修改5. 查询a>根据id查询用户信息b>根据多个id查询多个用户信息c>通过map条件查询用户信息d>查询所有数据6. 通用Servicea>IServi…

RK3588平台开发系列讲解(Display篇)开机视频的设置

平台内核版本安卓版本RK3588Linux 5.10Android 12文章目录 一、开机视频功能介绍二、使用方法2.1、开启与关闭2.2、视频放置位置2.3、编译结果2.4、视频素材要求2.5、参数控制说明沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇将介绍RK3588平台开机视频的使用方法…

ARM64内存虚拟化分析(7)stage2异常处理

当虚拟机访问内存或虚拟机访问寄存器时,由于并没有分配真实的物理地址,并没有建立stage2映射,因此这两种情况会产生stage2异常处理,其中第一种情况为真实的stage2缺页,第二种情况为MMIO处理。同时如果在stage2产生外部…

零膨胀负二项回归案例分析

零膨胀负二项回归分析 计数研究模型中,常用泊松回归模型,但泊松回归模型理论上是要求平均值与标准差相等,如果不满足,则可使用负二项回归模型,负二项回归放宽了平均值标准差这一理论假定。 在实际研究中,…

网络协议类型

网络协议是一组规则、约定和数据结构,用于规定设备如何跨网络交换数据。换句话说,网络协议可以等同于两个设备必须理解的语言,以实现信息的无缝通信,无论其基础设施和设计差异如何。 OSI 模型:网络协议的工作原理 要…

Oracle数据库安装配置详细教程汇总(含11g、12c、18c、19c、21c)

不论你是数据库小白,还是久经沙场的技术专家,你接触和运维Oracle数据库的第一步可能都是安装配置。并且随着软硬件的升级、替换以及业务场景的变化,数据库安装也将是你常常会进行的操作之一。 这里先为大家附上Oracle各版本支持的生命周期及…

阶段性回顾(2)

1. 移位操作符的对象只能是整数,只能对整数的二进制位进行移动。 2. 二进制是数值的一种表示形式。一个整数占了四个字节,相当于一个整数可以用32位二进制位序列表示,那么这时候该如何判断正负呢?规定:这32位二进制序列的头一位如…

第二十二讲:神州路由器OSPF单区域路由的配置

实验拓扑图如下所示 设备 端口 IP 子网掩码 网关 Router-A F0/0 172.16.1.1 255.255.255.0 无 F0/3 172.16.0.1 255.255.255.0 无 Router-B F0/0 172.16.1.2 255.255.255.0 无 F0/3 172.16.2.1 255.255.255.0 无 PC1 172.16.0.2 255.255.255.0 172.1…

解决docker容器因报错无法启动的问题,检查、修复容器错误并重启

问题复现 使用: sudo docker ps -a查看当前的docker容器: 我们想启动name为【docker-mongo】的这个容器,因此要执行 sudo docker start docker-mongo但是执行后仍旧没有重启,大概率是重启的时候报错了,查看日志&a…

Spring Boot骚操作-多数据源Service层封装

mysql, es, mongodb 三个数据源用配置文件方式连接,JPA只是正对dao做了封装,本文主要介绍如何对service层进行封装。 Spring Boot - 多个数据源Service层封装 类关系图 封装的一些配置 application.yml pom.xml 封装后使用 MySQL 动态数据访问 Mong…

坦克大战1.0,java时间处理机制

1.java 绘图坐标体系 1.1 坐标体系-介绍 下图说明了Java坐标系。坐标原点位于左上角,以像素为单位。在Java坐标系中,第一个是x坐标,表示当前位置为水平方向,距离坐标原点x个像素;第二个是y坐标,表示当前位置为垂直方向,距离坐标原点y个像素。…

金融业务如何高性能传输数据

对系统要求高,通常按金融级标准设计。金融数据传输要求速度快,流量大,极强容灾。 案例分析 简化版券商算法交易平台对接交易所: 涉及场景多。既有事务数据,也有市场数据模型简单。只涉及到2个主体复杂度可选。连接交…

【结构型】装饰模式(Decorator)

目录装饰模式(Decorator)适用场景装饰模式实例代码(Java)装饰模式(Decorator) 动态地给一个对象添加一些额外的职责。就增加功能而言,装饰模式 (Decorator) 比生成子类更加灵活。 适用场景 在不影响其他对象的情况下,以动态、透…

2023让工作效率直线飞升

2022年马上就要过去,过去的一年,腾讯云HiFlow和众多腾讯系应用也帮助了许多企业完成数字化转型,许多没有代码基础的业务人员也能通过搭建工作流,高效的处理生活&工作的繁杂事务,时间精力节省50%。把时间花在更重要…

开源 | 携程机票跨端 Kotlin DSL 数据库框架 SQLlin

作者简介禹昂,携程机票移动端资深工程师,专注于 Kotlin 移动端跨平台领域,Kotlin 中文社区核心成员,图书《Kotlin 编程实践》译者。一、背景2022年9月 Kotlin 1.7.20 发布之后,Kotlin Multiplatform Mobile&#xff08…