详细分析单例模式

news2025/4/17 13:17:20

目录

1.单例模式的定义

2.单例模式的实现方式

1.饿汉模式

2.懒汉模式

(1)线程不安全的问题怎么解决?

(2)直接对整个getInstance方法代码块加锁吗?

(3)那对if语句加锁不就行了吗?

(4)那在if语句内部加锁可以吗?

(5)应用双重if语句判断

(6)加入volatile防止指令重排序

3.相关的面试题

(1)为什么需要双重检查?

(2)为什么需要加volatile修饰?

(3)为什么静态内部类不需要加volatile修饰?

(4)单例模式的缺点是什么?


1.单例模式的定义

单例模式是一种创建型设计模式,确保一个类只有一个实例,并提供全局访问点。单例模式常用于管理共享资源(如数据库连接池、线程池、配置对象等)。

其中的设计模式指的是解决软件设计中常见问题的可复用方案,是面向对象编程的经验总结。它们分为 创建型结构型 和 行为型 三大类。简要说就是类似于框架,是大佬们提供的可供使用的一种“公式”。

2.单例模式的实现方式

1.饿汉模式

public class Singleton {
    private static final Singleton INSTANCE = new Singleton(); // 类加载时创建
    
    private Singleton() {} // 私有构造方法
    
    public static Singleton getInstance() {
        return INSTANCE;
    }
}

饿汉模式指的是在方法加载的同时就初始化实例(不管你用不用,我先开头就把实例创建了)

优点:简单,线程安全(JVM保证类加载时的线程安全)。

缺点:即便不使用也会照常创建方法,可能会造成资源浪费。

2.懒汉模式

public class Singleton {
    private static Singleton instance;
    
    private Singleton() {}
    
    public static Singleton getInstance() { 
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

懒汉模式指的是方法加载时不先创建实例,等到真正用到的时候再创建

优点:资源利用率高,避免不必要的实例创建。

缺点:带来了线程不安全的问题。

(1)线程不安全的问题怎么解决?

由于懒汉模式带来的线程不安全问题,我们要在代码中加入锁,也就是加入synchronized关键字。

(2)直接对整个getInstance方法代码块加锁吗?

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

像这样直接对整个getInstance方法修饰synchronized关键字固然可以解决线程安全问题。

但是由于整个getInstance方法只有在第一次创建实例时会进入if语句,存在线程安全隐患其他情况下,代码会略过if语句,直接return,也就不存在线程安全问题了

直接对整个方法加锁会导致不必要的开销变大 (加锁的开销),资源利用率降低

(3)那对if语句加锁不就行了吗?

public class Singleton {
    private static Singleton instance;
    
    private Singleton() {}
    
    public static Singleton getInstance() { 
       synchronized(Singleten.calss){
        if (instance == null) {
            instance = new Singleton();
        }
}
        return instance;
    }
}

这样写的代码,虽然没有对整个方法加锁,但是在代码执行过程中,不管有没有创建完成实例,都会对if语句加锁,然后再判断if语句。

这依然会导致性能低下。

(4)那在if语句内部加锁可以吗?

public class Singleton {
    private static Singleton instance;
    
    private Singleton() {}
    
    public static Singleton getInstance() { 
        if (instance == null) {
   synchronized(Singleten.calss){
            instance = new Singleton();
        }
}
        return instance;
    }
}

这样虽然解决了开销问题,但是在进入if语句之后,加锁之前,如果线程被调度走(抢走),其他线程创建了实例,代码继续执行,这时,锁才刚被加上。

这就会导致多次创建实例

(5)应用双重if语句判断

public class Singleton {
    private static volatile Singleton instance; // ✅ volatile 禁止指令重排序
    
    private Singleton() {}
    
    public static Singleton getInstance() {
        if (instance == null) {               // 第一次检查(无锁)
            synchronized (Singleton.class) {   // 加锁
                if (instance == null) {       // 第二次检查(有锁)
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

第一次if语句判断是否是第一次创建实例,减少全部加锁的不必要开销

第二次if语句判断加锁完成后其他线程是否创建了实例,防止多次创建实例

(6)加入volatile防止指令重排序

指令重排序是编译器和处理器为了优化程序性能,在不改变程序语义(单线程环境下最终执行结果)的前提下,对指令的执行顺序进行重新排列的一种优化手段。不过在多线程环境中,指令重排序可能会引发一些难以调试的问题。

因为实例的创建不是原子的,instance = new Singleton()分为三步:1.分配内存空间,2.初始化对象,3.将instance指向内存地址。

所以如果触发编译器的指令重排序,就有可能打乱instance = new Singleton()的三步的顺序,造成线程不安全。

public class Singleton {
    private static volatile Singleton instance; // ⚠️ 必须加 volatile
    
    private Singleton() {}
    
    public static Singleton getInstance() {
        if (instance == null) { // 第一次检查(无锁)
            synchronized (Singleton.class) { // 加锁
                if (instance == null) { // 第二次检查(有锁)
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

3.相关的面试题

(1)为什么需要双重检查?

第一重if检查是否是第一次创建实例,这样可以保证在以后得代码执行过程中直接跳过if语句代码块,减少不必要的开销(加锁)。

第二重if检查进入第一重if语句之后,加锁之前,线程是否有被调度走,实例是否已经被创建完毕。

避免多次创建实例。

(2)为什么需要加volatile修饰?

防止指令重排序。

创建实例的过程不是原子的,instance = new Singleton()分为三步:

(1)分配内存空间

(2)初始化对象

(3)istance指向内存空间

指令重排序可能会打乱这三步,导致其他线程拿到未创建的实例或者多次创建实例。

(3)为什么静态内部类不需要加volatile修饰?

public class Singleton {
    private Singleton() {}
    
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton(); // 类加载时创建
    }
    
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE; // 第一次调用时加载内部类
    }
}

因为JVM保证了线程安全,并且初始化是原子的。

(4)单例模式的缺点是什么?

(1)难以拓展,通常不允许子类化。

(2)隐藏了依赖关系

(3)长期持有对象可能增加内存压力。

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

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

相关文章

服务器报错:xxx/libc.so.6: version `GLIBC_2.32‘ not found

/lib/x86_64-linux-gnu/libc.so.6: version GLIBC_2.32 not found (required by ./aima-sim-app-main) 解决思路 根据错误信息,您的应用程序 aima-sim-app-main 和 libmujoco.so.3.1.6 库依赖于较新的 GNU C Library (glibc) 版本(如 GLIBC_2.32, GLIBC…

Flutter之页面布局一

目录: 1、页面布局一2、无状态组件StatelessWidget和有状态组件StatefulWidget2.1、无状态组件示例2.2、有状态组件示例2.3、在 widget 之间共享状态1、使用 widget 构造函数2、使用 InheritedWidget3、使用回调 3、布局小组件3.1、布置单个 Widget3.2、容器3.3、垂…

架构思维: 数据一致性的两种场景深度解读

文章目录 Pre案例数据一致性问题的两种场景第一种场景:实时数据不一致不要紧,保证数据最终一致性就行第二种场景:必须保证实时一致性 最终一致性方案实时一致性方案TCC 模式Seata 中 AT 模式的自动回滚一阶段二阶段-回滚二阶段-提交 Pre 架构…

大数据knox网关API

我们过去访问大数据组件,如sparkui,hdfs的页面,以及yarn上面看信息是很麻烦的一件事。要记每个端口号,比如50070,8090,8088,4007,如果换到另一个集群,不同版本&#xff0…

【Tauri2】015——前端的事件、方法和invoke函数

目录 前言 正文 准备 关键url 获取所有命令 切换主题set_theme 设置大小 获得版本version 名字name 监听窗口移动 前言 【Tauri2】005——tauri::command属性与invoke函数-CSDN博客https://blog.csdn.net/qq_63401240/article/details/146581991?spm1001.2014.3001.…

密码学基础——分组密码的运行模式

前面的文章中文我们已经知道了分组密码是一种对称密钥密码体制,其工作原理可以概括为将明文消息分割成固定长度的分组,然后对每个分组分别进行加密处理。 下面介绍分组密码的运行模式 1.电码本模式(ECB) 2.密码分组链接模式&…

Python----计算机视觉处理(Opencv:道路检测完整版:透视变换,提取车道线,车道线拟合,车道线显示,)

Python----计算机视觉处理(Opencv:道路检测之道路透视变换) Python----计算机视觉处理(Opencv:道路检测之提取车道线) Python----计算机视觉处理(Opencv:道路检测之车道线拟合) Python----计算机视觉处理&#xff0…

基于飞桨框架3.0本地DeepSeek-R1蒸馏版部署实战

深度学习框架与大模型技术的融合正推动人工智能应用的新一轮变革。百度飞桨(PaddlePaddle)作为国内首个自主研发、开源开放的深度学习平台,近期推出的3.0版本针对大模型时代的开发痛点进行了系统性革新。其核心创新包括“动静统一自动并行”&…

docker初始环境搭建(docker、Docker Compose、portainer)

docker、Docker Compose和portainer的安装部署、使用 docker、Docker Compose和portainer的安装部署、使用一.安装docker1.失败的做法2.首先卸载旧版本(没安装则下一步)3.配置下载的yum来源,不然yum search搜不到4.安装启动docker5.替换国内源…

开源RuoYi AI助手平台的未来趋势

近年来,人工智能技术的迅猛发展已经深刻地改变了我们的生活和工作方式。 无论是海外的GPT、Claude等国际知名AI助手,还是国内的DeepSeek、Kimi、Qwen等本土化解决方案,都为用户提供了前所未有的便利。然而,对于那些希望构建属于自…

element-ui自制树形穿梭框

1、需求 由于业务特殊需求,想要element穿梭框功能,数据是二级树形结构,选中左边数据穿梭到右边后,左边数据不变。多次选中左边相同数据进行穿梭操作,右边数据会多次增加相同的数据。右边数据穿梭回左边时,…

Linux系统学习Day04 阻塞特性,文件状态及文件夹查询

知识点4【文件的阻塞特性】 文件描述符 默认为 阻塞 的 比如:我们读取文件数据的时候,如果文件缓冲区没有数据,就需要等待数据的到来,这就是阻塞 当然写入的时候,如果发现缓冲区是满的,也需要等待刷新缓…

Python基础——Pandas库

对象的创建 导入 Pandas 时,通常给其一个别名“pd”,即 import pandas as pd。作为标签库,Pandas 对象在 NumPy 数组基础上给予其行列标签。可以说,列表之于字典,就如 NumPy 之于 Pandas。Pandas 中,所有数…

C++: 类型转换

C: 类型转换 (一)C语言中的类型转换volatile关键字 修饰const变量 (二)C四种强制类型转换1. static_cast2. reinterpret_cast3. const_cast4. dynamic_cast总结 (三)RTTI (一)C语言中的类型转换 在C语言中…

STM32——DAC转换

DAC简介 DAC,全称:Digital-to-Analog Converter,扑指数字/模拟转换器 ADC和DAC是模拟电路与数字电路之间的桥梁 DAC的特性参数 1.分辨率: 表示模拟电压的最小增量,常用二进制位数表示,比如&#xff1a…

Kafka的索引设计有什么亮点

想获取更多高质量的Java技术文章?欢迎访问Java技术小馆官网,持续更新优质内容,助力技术成长 Java技术小馆官网https://www.yuque.com/jtostring Kafka的索引设计有什么亮点? Kafka 之所以能在海量数据的传输和处理过程中保持高…

在深度学习中,如何统计模型的 ​​FLOPs(浮点运算次数)​​ 和 ​​参数量(Params)

在深度学习中,统计模型的FLOPs(浮点运算次数)和参数量(Params)是评估模型复杂度和计算资源需求的重要步骤。 一、参数量(Params)计算 参数量指模型中所有可训练参数的总和,其计算与…

Linux之Shell脚本--命令提示的写法

原文网址:Linux之Shell脚本--命令提示的写法-CSDN博客 简介 本文介绍Linux的Shell脚本命令提示的写法。 场景描述 在写脚本时经常会忘记怎么使用,需要进行命令提示。比如:输入-h参数,能打印用法。 实例 新建文件&#xff1a…

Day19 -实例:xcx逆向提取+微信开发者工具动态调试+bp动态抓包对小程序进行资产收集

思路: 拿到源码后的测试方向: Step1、xcx逆向提取源码 00x1 先将曾经使用小程序记录删除 00x2 访问小程序 例:汉川袁老四小程序 00x3 将文件给xcx进行逆向解包 xcx工具的目录下,wxpack文件夹内 Step2、微信开发者工具进行动态…

鸿蒙Arkts开发飞机大战小游戏,包含无敌模式,自动射弹,暂停和继续

飞机大战可以把飞机改成图片,目前包含无敌模式,自动射弹,暂停和继续的功能 代码如下: // 定义位置类 class GamePosition {x: numbery: numberconstructor(x: number, y: number) {this.x xthis.y y} }Entry Component struct…