单例模式的几种实现

2023-11-10

单例模式的几种实现

在开发中,我们总是会遇到使用单例模式的情况,今天就来总结一下几种实现单例模式的方法。
1.饿汉式

public class SingletonDemo1 {
    //类初始化时,立即加载该对象(没有延时加载的优势)!由于加载类时,天然的线程安全!
    private static SingletonDemo1 instance = new SingletonDemo1();
    //私有构造器
    private SingletonDemo1(){}
    //方法没有同步,调用效率高
    public static SingletonDemo1 getInstance(){
        return instance;
    }
}

不管是否使用到该类,都先初始化该类,有可能会造成资源浪费!
2.懒汉式, 有两种实现方式,
a).方式一

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

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

这种写法只能在单线程下使用。如果是多线程,可能发生一个线程通过并进入了 if (singleton == null) 判断语句块,但还未来得及创建新的实例时,另一个线程也通过了这个判断语句,两个线程最终都进行了创建,导致多个实例的产生。所以在多线程环境下必须摒弃此方式。
这种写法线程不安全。
b).加入同步锁

public class SingletonDemo2 {
    // 类初始化时,不初始化这个对象(延时加载,懒加载)
    private static SingletonDemo2 instance;
    // 私有构造器
    private SingletonDemo2() {
    }
    // 方法同步,调用效率低
    public static synchronized SingletonDemo2 getInstance() {
        if (instance == null) {
            instance = new SingletonDemo2();
        }
        return instance;
    }
}

通过为 getInstence() 方法增加 synchronized 关键字,迫使每个线程在进入这个方法前,要先等候别的线程离开该方法,即不会有两个线程可以同时进入此方法执行 new SingletonDemo2(),从而保证了单例的有效。但它的致命缺陷是效率太低了,每个线程每次执行 getInstance() 方法获取类的实例时,都会进行同步。而事实上实例创建完成后,同步就变为不必要的开销了,这样做在高并发下必然会拖垮性能。所以此方法虽然可行但也不推荐。
网上这样的代码更多,可以很好的工作,但是缺点是效率低。
3. 双重检查锁式。
实际上,早在JDK1.5就引入volatile关键字,所以又有了一种更好的双重校验锁写法。

public class Singleton {
    private volatile static Singleton singleton;
    public Singleton() {
    }

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

DCL体现在进行了两次 if (singleton == null) 的检查,这样既同步代码块保证了线程安全,同时实例化的代码也只会执行一次,实例化后同步操作不会再被执行,从而效率提升很多。
双重检查锁定(DCL)方式也是延迟加载的,它唯一的问题是,由于 Java 编译器允许处理器乱序执行,在 JDK 版本小于 1.5 时会有 DCL 失效的问题。当然,现在大家使用的 JDK 普遍都已超过 1.4,只要在定义单例时加上 1.5 及以上版本具体化了的 volatile 关键字,即可保证执行的顺序,从而使单例起效。
Android 中鼎鼎大名的 Universal Image Loader 和 EventBus 都是采用了这种方式的单例
4).静态内部类式

public class SingletonDemo3 {
    //静态内部类
    private static class SingletonClassGetInstance {
        public static final SingletonDemo3 instance = new SingletonDemo3();
    }
    // 私有构造器
    private SingletonDemo3() {
    }
    public static  SingletonDemo3 getInstance() {
        return SingletonClassGetInstance.instance;
    }
}

这种方式是SingletonDemo3 类被装载了,instance不一定被初始化。因为SingletonClassGetInstance 类没有被主动使用,只有显示通过调用getInstance方法时,才会显示装载SingletonClassGetInstance 类,从而实例化instance。
5). 枚举式模式

public enum SingletonDemo4 {
    //这个枚举对象本身就是单例对象
    INSTANCE;
    //添加自己需要的操作
    public void operation(){
    }
}

这种模式下,线程安全,调用效率高,但是不能延时加载!实际工作中,少有人使用这种方式!难道是因为太高级了!O(∩_∩)O~
小结
a. 单例模式有五种实现方式
1).饿汉式
线程安全,调用效率高,但是不能延时加载
2).懒汉式
线程安全,调用效率不高,并且可以延时加载
3).双重检查锁式
由于JVM底层内部模型原因,偶尔会出问题,不建议使用
4).静态内部类式
线程安全,调用效率高,并且可以延时加载。推荐使用
5).枚举式
线程安全,调用效率高,不能延时加载,但是可以天然的防止反射和反序列化漏洞
我们在编码中,有关单例模式的具体实现方式,需要根据实际情况来选择!

b. 单例模式中的存在的问题
如果单例类实现了java.io.Serializable接口,那么这个类可能会被反序列化,并且反序列化多次同一对象时,会得到多个单例类的实例。这样就不是单例了!
解决办法,需要在单例类中添加如下方法,


    // 反序列化时,如果定义了readResovle() 则直接返回此方法指定的对象,而不需要单独再创建新对象!
    private Object readResovle() throws ObjectStreamException {
        // TODO Auto-generated method stub
        return instance;
    }

PS:在Android中使用单例模式可能会内存泄露。
当调用getInstance时,如果传入的context是Activity的context。只要这个单例没有被释放,那么这个Activity也不会被释放一直到进程退出才会释放。

public class CommUtils {

    private volatile static CommUtils mCommUtils;

    private Context mContext;
    public CommUtils(Context context) {
        mContext=context;
    }

    public static  CommUtils getInstance(Context context) {
        if (mCommUtils == null) {
            synchronized (CommUtils.class) {
                if (mCommUtils == null) {
                    mCommUtils = new CommUtils(context);
                }
            }
        }
        return mCommUtils;
    }
}

解决办法
能使用Application的Context就不要使用Activity的Content,Application的生命周期伴随着整个进程的周期

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

单例模式的几种实现 的相关文章

随机推荐

  • IP总数,子网掩码查询表

    IP总数 子网掩码查询表 不同位数的网络号对应的子网以及主机数
  • 刷脸支付面向全国诚招实力合作商政策置顶

    刷脸支付在各大商店 餐馆逐渐铺开 消费者在购物付款时 不用打开手机二维码 只是看一眼支付设备就能轻松完成付款 从到地方的媒体 都在关注 刷脸支付 甚至会认为刷脸支付会代替现在的流行的二维码支付 成为未来支付的主流 刷脸支付正在掀起新零售新浪
  • 2022人生除生死外,无大事——一边治愈,一边前行!

    今天看完了 人生大事 这个电影 灵动的小姑娘 痞帅的朱一龙 一个殡葬师的生活在这个小姑娘的闯入后 开始有了波澜 活力 他逐渐找到了生活的意义和方向 挺好评的一部电影 虽然是关于离别 死亡 却是在很欢快嘈杂的情境下穿插着的 有泪有笑有温情 人
  • NRF24L01 一对三通信

    注意 NRF24L01 无论是一对一通信 还是一对多通信 其实本质上同一时刻是一对一通信 它是属于半双工通信 你只需要先调通一对一的通信 无论是一对2 一对六也是同一个原理 基础SPI知识和NRF24L01基础知识不多说 一 项目背景 1
  • jupyter lab使用

    记录一下使用过程中遇到的问题以及解决方法 网上资料感觉说的不是很明白 还是截图好用 增加行序号 自动换行 折叠代码 查找及替换 malplotlib 增加行序号 自动换行 折叠代码 打开setting 进入setting editor页面
  • xtrabackup 全量备份、恢复数据

    1 全量备份 root localhost lib innobackupex defaults file defaults file user mysql username password mysql password stream ta
  • seaborn.distplot()

    参考https www cntofu com book 172 docs 24 md 一 函数 seaborn distplot a bins None hist True kde True rug False fit None hist
  • 在家干什么副业好?工作之余做什么副业比较好

    现在真的到了一个副业时代 人人都可以做副业 不仅可以增加一份收入 还可以拓宽视野 增加阅历 如果是在家里做副业的话 那么还是推荐网络短视频 直播和写作 这也是普通人最容易上手的副业 1 拍游戏视频 既然大家这么喜欢玩游戏 那就把平时刷视频的
  • layui的layer弹出层和form表单

    参考文献 含代码 http www shagua wiki project 3 p 113
  • 【Git】如何在Vscode中使用码云(Gitee)实现远程代码仓库与本地同步?(新手图文教程)

    一 安装git 直接参照下面的链接就OK Git Git的下载 安装与配置教程 图文 敦厚的曹操的博客 CSDN博客 git下载教程 Git Git的下载与安装教程 图文https blog csdn net dxnn520 article
  • 高防CDN和高防IP防护的不同之处

    网站的运行离不开服务器 服务器在使用的过程中不可避免的会遇到一些攻击 需要做好相应的安全防护 那么 在安全防护上 服务器使用CDN与高防IP具体有什么不同之处呢 一 IP数量 CDN是一组ip的防护 是共享的ip 高防ip独享的ip防护 二
  • 2021-10-17 stm32f103 睡眠模式唤醒

    1 开启 睡眠时钟 2 打开 WFI 3 GPIO 1 GPIO内部电路图 下面转载 https blog csdn net xiewenhao12 article details 89030398 1 根据设备原理图查看IO外部引脚连接电
  • 13.108.Spark 优化、Spark优化与hive的区别、SparkSQL启动参数调优、四川任务优化实践:执行效率提升50%以上

    13 108 Spark 优化 1 1 25 Spark优化与hive的区别 1 1 26 SparkSQL启动参数调优 1 1 27 四川任务优化实践 执行效率提升50 以上 13 108 Spark 优化 1 1 25 Spark优化与
  • pluto实现分析(5)

    本文档的Copyleft归yfydz所有 使用GPL发布 可以自由拷贝 转载 转载时请保持文档的完整性 严禁用于任何商业用途 msn yfydz no1 hotmail com 来源 http yfydz cublog cn 7 内核接口
  • ABAP 发邮件(三)

    转自http blog sina com cn s blog 7c7b16000101bnxk html SAP ABAP 发邮件方法三 OO Report ZSENDEMAIL08 REPORT zsendemail08 CONSTANT
  • R:数据对象及类型

    数据对象 R语言创建和控制的实体被称为对象 object 它们可以说变量 数组 字符串 函数或者其它通过这些实体定义的更一般的结构 structures 在R语言里 对象是通过名字创建和保存的 R对象的名称必须以一个英文字母打头 并由一串大
  • 【Vue】动态监听localStorage或sessionStorage中数据的变化:

    1 需求 一个页面数据变化要改变另一个页面 甚至整个项目的数据变化 2 在main js中 description 监听本地储存的值变化 param number type 1 localStorage 2 sessionStorage p
  • QT在vs2019上配置流程

    一 安装VS2019 2 安装Qt5 15 VS2019只能安装QT5 14版本以上 5 15版本以上需要在在线安装平台下载安装 在QT官网https download qt io 中找到official releases gt onlin
  • Anaconda安装深度学习框架(TensorFlow,Pytorch)教程

    有道云链接 1 用anaconda创建虚拟环境 在这之前 我们需要确定服务器上安装了anaconda 使用下面命令查看 whereis anaconda anaconda data softwares opt anaconda2 bin a
  • 单例模式的几种实现

    单例模式的几种实现 在开发中 我们总是会遇到使用单例模式的情况 今天就来总结一下几种实现单例模式的方法 1 饿汉式 public class SingletonDemo1 类初始化时 立即加载该对象 没有延时加载的优势 由于加载类时 天然的