单例模式的五种写法

Twin Builder CFD 示例:构建、验证和评估静态 ROM 通过我们构建、验证和评估静态 ROM 的综合指南,释放 Twin Builder CFD 的潜力。 阅读详情

单例模式

单例模式是最常用到的设计模式之一,熟悉设计模式的朋友对单例模式都不会陌生。一般介绍单例模式的书籍都会提到 饿汉式 和 懒汉式 这两种实现方式。但是除了这两种方式,本文还会介绍其他几种实现单例的方式,让我们来一起看看吧。

简介

单例模式是一种常用的软件设计模式,其定义是单例对象的类只能允许一个实例存在。

许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。

一般来说,单例模式有五种写法:懒汉、饿汉、双重检验锁、静态内部类、枚举

基本的实现思路
单例模式要求类能够有返回对象一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法,通常使用getInstance这个名称)。

单例的实现主要是通过以下两个步骤:

1.将该类的构造方法定义为私有方法,这样其他处的代码就无法通过调用该类的构造方法来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例;
2. 在该类内提供一个静态方法,当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用。

注意事项
单例模式在多线程的应用场合下必须小心使用。
1.如果当唯一实例尚未创建时,有两个线程同时调用创建方法,那么它们同时没有检测到唯一实例的存在,从而同时各自创建了一个实例,这样就有两个实例被构造出来,从而违反了单例模式中实例唯一的原则。 解决这个问题的办法是为指示类是否已经实例化的变量提供一个互斥锁(虽然这样会降低效率)。
2.当其中一个未检测到实例为null而使用getInstance时,会报空指针异常(双检问题)

如何正确地写出单例模式

单例模式算是设计模式中最容易理解,也是最容易手写代码的模式了吧。但是其中的坑却不少,所以也常作为面试题来考。本文主要对几种单例写法的整理,并分析其优缺点。很多都是一些老生常谈的问题,但如果你不知道如何创建一个线程安全的单例,不知道什么是双检锁,那这篇文章可能会帮助到你。

懒汉式,线程不安全

当被问到要实现一个单例模式时,很多人的第一反应是写出如下的代码,包括教科书上也是这样教我们的。

public class Singleton {
    private static Singleton instance;
    private Singleton (){}

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

这段代码简单明了,而且使用了懒加载模式,但是却存在致命的问题。当有多个线程并行调用 getInstance() 的时候,就会创建多个实例。也就是说在多线程下不能正常工作。

懒汉式,线程安全

为了解决上面的问题,最简单的方法是将整个 getInstance() 方法设为同步(synchronized)。

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

虽然做到了线程安全,并且解决了多实例的问题,但是它并不高效。因为在任何时候只能有一个线程调用 getInstance() 方法。但是同步操作只需要在第一次调用时才被需要,即第一次创建单例实例对象时。这就引出了双重检验锁。

双重检验锁

双重检验锁模式(double checked locking pattern),是一种使用同步块加锁的方法。程序员称其为双重检查锁,因为会有两次检查 instance == null,一次是在同步块外,一次是在同步块内。为什么在同步块内还要再检验一次?因为可能会有多个线程一起进入同步块外的 if,如果在同步块内不进行二次检验的话就会生成多个实例了。

public static Singleton getSingleton() {
    if (instance == null) {                         //Single Checked
        synchronized (Singleton.class) {
            if (instance == null) {                 //Double Checked
                instance = new Singleton();
            }
        }
    }
    return instance ;
}

这段代码看起来很完美,很可惜,它是有问题。主要在于instance = new Singleton()这句,这并非是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情。

  1. 给 instance 分配内存
  2. 调用 Singleton 的构造函数来初始化成员变量
  3. 将instance对象指向分配的内存空间(执行完这步 instance 就为非 null 了)

但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。

我们只需要将 instance 变量声明成 volatile 就可以了。

public class Singleton {
    private volatile static Singleton instance; //声明成 volatile
    private Singleton (){}

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

有些人认为使用 volatile 的原因是可见性,也就是可以保证线程在本地不会存有 instance 的副本,每次都是去主内存中读取。但其实是不对的。使用 volatile 的主要原因是其另一个特性:禁止指令重排序优化。也就是说,在 volatile 变量的赋值操作后面会有一个内存屏障(生成的汇编代码上),读操作不会被重排序到内存屏障之前。比如上面的例子,取操作必须在执行完 1-2-3 之后或者 1-3-2 之后,不存在执行到 1-3 然后取到值的情况。从「先行发生原则」的角度理解的话,就是对于一个 volatile 变量的写操作都先行发生于后面对这个变量的读操作(这里的“后面”是时间上的先后顺序)。

但是特别注意在 Java 5 以前的版本使用了 volatile 的双检锁还是有问题的。其原因是 Java 5 以前的 JMM (Java 内存模型)是存在缺陷的,即时将变量声明成 volatile 也不能完全避免重排序,主要是 volatile 变量前后的代码仍然存在重排序问题。这个 volatile 屏蔽重排序的问题在 Java 5 中才得以修复,所以在这之后才可以放心使用 volatile。

相信你不会喜欢这种复杂又隐含问题的方式,当然我们有更好的实现线程安全的单例模式的办法。

饿汉式 static final field

这种方法非常简单,因为单例的实例被声明成 static 和 final 变量了,在第一次加载类到内存中时就会初始化,所以创建实例本身是线程安全的。

public class Singleton{
    //类加载时就初始化
    private static final Singleton instance = new Singleton();
    
    private Singleton(){}

    public static Singleton getInstance(){
        return instance;
    }
}

这种写法如果完美的话,就没必要在啰嗦那么多双检锁的问题了。缺点是它不是一种懒加载模式(lazy initialization),单例会在加载类后一开始就被初始化,即使客户端没有调用 getInstance()方法。饿汉式的创建方式在一些场景中将无法使用:譬如 Singleton 实例的创建是依赖参数或者配置文件的,在 getInstance() 之前必须调用某个方法设置参数给它,那样这种单例写法就无法使用了。

静态内部类 static nested class

我比较倾向于使用静态内部类的方法,这种方法也是《Effective Java》上所推荐的。

public class Singleton {  
    private static class SingletonHolder {  
        private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
        return SingletonHolder.INSTANCE; 
    }  
}

这种写法仍然使用JVM本身机制保证了线程安全问题;由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它,因此它是懒汉式的;同时读取实例的时候不会进行同步,没有性能缺陷;也不依赖 JDK 版本。

枚举 Enum

用枚举写单例实在太简单了!这也是它最大的优点。下面这段代码就是声明枚举实例的通常做法。

public enum EasySingleton{
    INSTANCE;
}

我们可以通过EasySingleton.INSTANCE来访问实例,这比调用getInstance()方法简单多了。创建枚举默认就是线程安全的,所以不需要担心double checked locking,而且还能防止反序列化导致重新创建新的对象。但是还是很少看到有人这样写,可能是因为不太熟悉吧。

总结

一般来说,单例模式有五种写法:懒汉、饿汉、双重检验锁、静态内部类、枚举。上述所说都是线程安全的实现,文章开头给出的第一种方法不算正确的写法。

就我个人而言,一般情况下直接使用饿汉式就好了,如果明确要求要懒加载(lazy initialization)会倾向于使用静态内部类,如果涉及到反序列化创建对象时会试着使用枚举的方式来实现单例。

Read More

基于YOLOv26的护栏损坏检测系统原理讲解 本文介绍了一种基于YOLOv26深度学习的护栏损坏检测系统。该系统通过车载摄像头或固定监控采集道路护栏图像,利用改进的YOLOv26算法实现高效准确的损坏检测。系统采用分层架构设计,包含数据采集、检测处理、分析评估和应用服务四个层次,能够自动识别变形、断裂、缺失等五种常见损坏类型,并评估损坏严重程度。相比传统人工检测方法,该系统大幅提高了检测效率和准确性,降低了人工成本和安全风险。关键技术包括多尺度特征融合、注意力机制和自适应非极大值抑制等创新设计,使系统在复杂道路环境下仍能保持稳定的检测性能。 阅读详情

相关推荐

告别简陋行情!在Ubuntu24.04系统用Wine9.0跑同花顺远航版的全流程记录

本文详细记录了在Ubuntu 24.04系统上,通过Wine 9.0兼容层成功安装并流畅运行同花顺远航版的全过程。文章对比了该方案与官方简陋Linux原生版本的功能差异,重点解决了高分辨率屏幕下的字体与界面显示问题,并提供了关键配置调优与长期维护建议,为Linux用户提供了功能完备的专业金融行情解决方案。

weixin_29207533的博客 1071

设计模式之----单例模式

单例模式 1.适用场合: (1)需要频繁的进行创建和销毁的对象; (2)创建对象时耗时过多或耗费资源过多,但又经常用到的对象; (3)工具类对象; (4)频繁访问数据库或文件的对象。 2.单例模式可以分为懒汉式和饿汉式: 懒汉式单例模式:在类加载时不初始化。 饿汉式单例模式:在类加载时就完成了初始化,所以类加载比较慢,但获取对象的速度快。 单例模式的八种写法: (1)饿汉式(静态常量)[可用] p...

Felix_ar 449

一文带你弄懂thermal governor IPA

当current temp < target temp时,使用k_pu,当current temp >= target temp时,使用k_po。如果current_temperature = switch_on_temp时,则k_p = k_pu,这时Pmax。如果current_temperature = target_temp时,则k_p = k_po,这时Pmax。2、解析dts,获得switch_on_temp和target_temp。3、初始化k_pu/k_po/k_i/k_d的常数。

yxw0609131056的博客 1245

你真的会单例吗?单例的八种写法

说起单例设计模式因该很多人都不陌生,按理说只有接触过java的都应该知道这个设计模式,但是你真的会写吗?还是说只会懒汉式和饿汉式?其实日常工作中随便掌握一种就已经可以了,但是我们身为java程序员应该不断的学习,即使用不到但是也要来了解以下,接下来我就来给你们写单例的八种写法,其中只有四种是能有并且没有问题的,其他的都有问题 写法一:传统的饿汉式 class Singleton01{ priva...

yuge1123的博客 1万+

基恩士数字光纤放大器

在2路输出类型上此为通道开关,并未配备0路类型;设置已搞不清楚不知道怎么办时···完成初始化后,模块重新显示当前值。①无工件时短按[SET]①有工件时短按[SET]各部位的名称和基本操作。可以手动微调设置值。

大小曲奇的博客 1万+

别再手动算重量了!用C++给UG/NX写个自动计算零件质量与质心的插件(附完整源码)

本文详细介绍了如何使用C++为UG/NX开发智能质量分析插件,实现零件质量与质心的自动计算。通过结合NXOpen和UF_MODL API,设计高效的数据结构和批量处理机制,大幅提升机械设计效率。文章包含完整源码和实用技巧,帮助工程师告别手动计算,适用于复杂装配体的自动化工程计算。

weixin_30247307的博客 130

那些你知道或不知道的单例写法(10种)

单例的写法有很多,可能还有我不知道的写法,每种都有其优缺点,适用场景也不同,所以需要根据实际情况选择具体的方法。多知道一种方法,可能在下次遇到特定场景时会给你多一种选择,这种选择可能会比饿汉模式、懒汉模式更简洁、更优雅。

赵侠客 694

量化爬虫第一步:手把手教你搞定同花顺问财的hexin-v参数(附JS逆向实战)

本文详细介绍了如何通过JS逆向技术破解同花顺问财数据接口的核心加密参数hexin-v,构建稳定可靠的量化数据采集方案。从抓包分析到Cookie Hook技术应用,再到环境补全与参数生成实现,手把手教你完成量化爬虫的第一步。文章还提供了实战注意事项与优化建议,帮助开发者规避常见问题。

weixin_42620202的博客 301

单例的七种写法

转载自http://cantellow.iteye.com/blog/838473

SoftwareOscar的博客 802

国产系统迁移实战:在银河麒麟V10上搞定Wine32位支持,让老Windows软件跑起来

本文详细介绍了在银河麒麟V10操作系统上构建Wine32位支持的全过程,帮助用户解决Windows应用迁移难题。从系统架构差异分析到Wine环境配置,再到实战应用部署与优化,提供了一套完整的解决方案,确保老Windows软件在国产系统上稳定运行。

weixin_32288959的博客 648

【异常】OpenClaw 飞书端 OpenClaw: access not configured. Pairing code: [] Ask the bot owner to approve with

摘要: OpenClaw飞书机器人交互中出现的access not configured报错,是因用户或组织未配置访问权限触发的拦截提示。报错包含组织单元ID和配对校验码,需管理员审批开通权限。普通用户需提交完整报错信息给管理员处理;管理员可通过执行标准化命令单次审批,或批量配置组织白名单优化权限管理。建议提前梳理权限范围、及时同步组织架构变更,并定期审计权限列表以确保安全合规。

奶爸的编程之路,也就一周冷个三天 370

单例模式的六种写法

单例模式的六种写法 重新回顾一下设计模式中的单例模式。 单例模式就是保证一个类只有一个实例,并提供一个访问它的全局访问点。使用场景: 整个项目需要一个共享访问点或共享数据。 创建一个对象需要耗费的资源过多,比如访问I/O或者数据库等资源。 工具类对象。 单例模式有很多种写法,最常见有六种写法。 1.饿汉式 public class Singleton { //饿汉式 private Singleton(){} private static Singleton instance =

feng海涛 1765

蓝牙功耗优化实战:连接蓝牙耳机时高耗电问题的深度排查与解决

连接任意一款蓝牙耳机, 进行插入实网SIM卡, 同时连接蓝牙耳机后, 进入IDLE模式, 测试时间为10分钟的测试结果, 平均电流毫安在8左右. 测试结果1: 平均电流 8.84mA 测试结果2: 平均电流 7.39mA 厘清问题: 由于出现上两幅图中 绿色线圈中的 20mA 左右高耗电时段, 从而将整体的平均电流抬高. 于是在记录IDLE下电流出现绿色线圈中的 20mA 左右高耗电时段, 同时录制UART1 Catcher log. Filter: MOD_BT/MOD_ATC.

专业开发者的博客,一个从事Android WiFi和蓝牙的工程师 1001

单例模式五种写法

一般来说,单例模式有五种写法:饿汉、懒汉、双重检验锁、静态内部类、枚举一般情况下,不建议使用第 2 种懒汉方式,建议使用第 1 种饿汉方式。只有在要明确实现 lazy loading 效果时,才会使用第 4 种静态内部类登记方式。如果涉及到反序列化创建对象时,可以尝试使用第 5 种枚举方式。如果有其他特殊的需求,可以考虑使用第 3 种双检锁方式。单例类的特点:(1)单例类确保自己只有一个实例(2)单例类必须自己创建自己的实例(3)单例类必须为其他对象提供唯一的实例。

zhuge_long的专栏 1309

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

JAVA单例模式的五种写法

单例模式(Singleton)应该是大家接触的第一个设计模式,其写法相较于其他的设计模式来说并不复杂,核心理念也非常简单:程序从始至终只有同一个该类的实例对象。

sinat_32501475的博客 294

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

Java单例模式的五种写法

单例模式:一个类只能有一个实例,并且提供一个全局可以访问的入口,好处:节省内存,节省计算,如当一个类才初始化的时候做了很多操作(读取数据库数据,而且数据库数据不变),那全局用同一个实例就行,每次都创建一个新的实例属于浪费资源 单例模式的适用场景: 1、无状态工具类,如日志工具,字符串工具 2、全局信息类,如全局计数,环境变量 第一种写法 饿汉式:在类加载时就创建了实例,而没有使用懒加载的方式,如果自始至终都没有使用这个实例,就造成了内存的浪费 public class Singleton {

chenchaozzu的博客 812

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

C# 单例模式的五种写法

C# 单例模式的五种写法及优劣分析,见下文: [单例模式及常见写法](http://blog.youkuaiyun.com/jiankunking/article/details/50867050) ...

weixin_30776545的博客 77

实例化几种方式_单例模式的五种写法,你会几种?

基本介绍单例模式(Singleton)应该是大家接触的第一个设计模式,其写法相较于其他的设计模式来说并不复杂,核心理念也非常简单:程序从始至终只有同一个该类的实例对象。举一个耳熟能详的例子,比如LOL中的大龙,一场游戏下来无论如何只有一只,所以该类只能被实例化一次。再举一个我们应用程序开发中常见的例子,Spring框架中的Bean作用范围默认也是单例的。我相信大家都知道单例的两种最基本的写法:饿汉...

weixin_29893445的博客 629
上一篇: hadoop执行任务时常见exception
下一篇: 分库分表
牧竹子
博客等级 码龄17年 572粉丝 233原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值