摘要:这篇文章主要是总结了一下9种常见的单例模式。
前言:单例模式我相信大家都不陌生,但是可能仅仅停留在“会用”的阶段。为什么会有这个模式?为什么要用这个模式?在哪里用单例模式最合适?乱用了会有什么负面影响?这些可能大多数人都一知半解。接下来就让我们大家一起来扒光单例模式的外衣,深入了解一下单例模式。
单例模式的定义
单例模式(Singleton Pattern):确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。 单例模式是一种对象创建型模式。
从其定义我们可以看出来单例模式存在三个要点:
- 实例唯一性
- 自行创建
- 全局访问
单例模式的九种写法
一、饿汉式(静态常量)
1 | /** |
- 优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
- 缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。
二、饿汉式(静态代码块)
1 | /** |
- 优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
- 缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。
三、懒汉式(线程不安全)
1 | /** |
- 优点:懒加载,只有使用的时候才会加载。
- 缺点:但是只能在单线程下使用。如果在多线程下,一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式。
四、懒汉式(线程安全)
1 | /** |
- 优点:懒加载,只有使用的时候才会加载,获取单例方法加了同步锁,保障线程安全。
- 缺点:效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。
五、懒汉式(线程安全,同步代码块)
1 | /** |
- 优点:改进了第四种效率低的问题。
- 缺点:不能完全保证单例,假如一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。
六、双重检查(DCL)
1 | /** |
- 优点:线程安全;延迟加载;效率较高。
- 缺点:JDK < 1.5 的时候不可用。不可用原因:由于volatile关键字会屏蔽Java虚拟机所做的一些代码优化,可能会导致系统运行效率降低,而JDK 1.5 以及之后的版本都修复了这个问题。(面试装逼用,谨记!!!)
七、静态内部类
1 | /** |
- 优点:避免了线程不安全,延迟加载,效率高。
- 缺点:暂无,最推荐使用。
- 特点:这种方式跟饿汉式方式采用的机制类似,但又有不同。
- 两者都是采用了类装载的机制来保证初始化实例时只有一个线程。不同的地方在饿汉式方式是只要Singleton类被装载就会实例化,没有Lazy-Loading的作用,而静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
八、枚举
1 | /** |
优点:不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。
缺点:JDK 1.5之后才能使用。
九、容器类管理
1 | /** |
- 优点:在程序的初始,将多种单例类型注入到一个统一的管理类中,在使用时根据key获取对象对应类型的对象。这种方式使得我们可以管理多种类型的单例,并且在使用时可以通过统一的接口进行获取操作, 降低了用户的使用成本,也对用户隐藏了具体实现,降低了耦合度。
- 缺点:不常用,有些麻烦
九种写法的优劣对比

一:不可用原因:由于volatile关键字会屏蔽Java虚拟机所做的一些代码优化,可能会导致系统运行效率降低,而JDK 1.5 以及之后的版本都修复了这个问题。
二:这种方式跟饿汉式方式采用的机制类似,但又有不同。两者都是采用了类装载的机制来保证初始化实例时只有一个线程。不同的地方在饿汉式方式是只要Singleton类被装载就会实例化,没有Lazy-Loading的作用, 而静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
单例模式在日常开发中的应用场景
- 图片加载
- 网络请求
- 工具类封装
合理的辨析一个设计是否应该为单例模式前,我们应该先问问自己几个问题,也是检验标准:
- 每一个应用(组件/模块)是否以完全一致的方式来使用这个类?
- 每一个应用(组件/模块)是否真的只需要这个类的一个实例呢?
- 对于这个类的客户端类来说,对他们自己是应用中的一部分这件事是否应该保持毫无察觉的状态呢?
如果我们对于以上这3条均给出了“是的”的答案,那么这个类就是可以被设计为单例模式了。反之还是不要用的好。
单例模式的优点
- 可以减少系统内存开支。
- 减少系统性能开销。
- 避免对资源的多重占用、同时操作。
单例模式的缺点
违反了单一责任链原则,测试困难
单例类的职责过重,在一定程度上违背了“单一职责原则”。因为单例类既充当了工厂角色,提供了工厂方法,同时又充当了产品角色,包含一些业务方法,将产品的创建和产品的本身的功能融合到一起。
扩展困难
由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。修改功能必须修改源码。
共享资源有可能不一致。
现在很多面向对象语言(如Java、C#)的运行环境都提供了自动垃圾回收的技术,因此,如果实例化的共享对象长时间不被利用,系统会认为它是垃圾,会自动销毁并回收资源,下次利用时又将重新实例化,这将导致共享的单例对象状态的丢失。
- 本文作者: th3ee9ine
- 本文链接: https://www.blog.ajie39.top/2021/05/05/9种单例模式总结/
- 版权声明: 本博客所有文章除特别声明外,均采用 LICENSE 下的许可协议。转载请注明出处!