一、单例模式介绍
1. 解决的问题
保证一个类只有一个实例。 最常见的是控制某些共享资源 (例如数据库或文件) 的访问权限。
运作方式是这样的: 如果创建了一个对象,同时过一会儿后决定再创建一个新对象,此时会获得之前已创建的对象, 而不是一个新对象。
注意, 普通构造函数无法实现上述行为,因为构造函数的设计决定了它必须总是返回一个新对象。
为该实例提供一个全局访问节点。 还记得用过的那些存储重要对象的全局变量吗?它们在使用上十分方便,但同时也非常不安全,因为任何代码都有可能覆盖掉那些变量的内容,从而引发程序崩溃。
和全局变量一样, 单例模式也允许在程序的任何地方访问特定对象。同时可以保护该实例不被其他代码覆盖。
2. 定义
单例模式是一种创建型设计模式,能够保证一个类只有一个实例,并提供一个访问该实例的全局节点。
3. 应用场景
- 程序中的某个类对于所有客户端只有一个可用的实例,可以使用单例模式。
- 要更加严格地控制全局变量,可以使用单例模式。
注意:可以随时调整限制并设定生成单例实例的数量,只需修改 获取实例
方法,即 getInstance
中的代码即可实现。
4. 与其他设计模式的关系
- 抽象工厂模式、生成器模式和原型模式都可以用单例来实现。
- 外观模式类通常可以转换为单例模式类,因为在大部分情况下一个外观模式就足够了。
- 如果能将对象的所有共享状态简化为一个享元对象,那么享元模式就和单例模式类似。但这个两个设计模式有两个根本性的不同。
- 只会有一个单例实体,但享元类可以有多个实体,各实体的内在状态也可以不同。
- 单例对象可以是可变的。享元对象是不可变的。
二、单例模式优缺点
1. 优点
- 可以保证一个类只有一个实例。
- 获得了一个指向该实例的全局访问节点。
- 仅在首次请求单例对象时对其进行初始化。
2. 缺点
- 违反了单一职责原则。
- 单例模式可能掩盖不良设计,比如程序各组件之间相互了解过多等。
- 该模式在多线程环境下需要进行特殊处理,避免多个线程多次出创建单例对象。
- 单例的客户端代码单元测试可能会比较困难,因为许多测试框架以基于继承的方式创建模拟对象。由于单例类的构造函数是私有的,而且绝大部分语言无法重写静态方法,所以需要仔细考虑模拟单例的方法。
三、7 种单例模式实现
静态类使用
1 | /** |
- 这种方式在我们的业务开发场景中非常常见,这样的方式可以在第一次运行的时候直接初始化 Map 类。
- 在不需要维持任何状态,不需要延迟加载,仅仅用于全局访问时,这种方式更便于使用。
- 但在需要被继承、需要维持一些特定状态时,单例模式更适合。
单例模式的实现方式比较多,主要是实现上是否支持懒汉模式、是否线程安全。
接下来通过不同的单例模式实现方式来解析单例模式。
1. 懒汉单例模式(线程不安全)
1 | /** |
这种懒汉单例模式满足了懒加载,但当多个访问者同时获取对象实例时(多进程),会导致多个实例并存,没有达到单例模式的需求。
2. 懒汉单例模式(线程安全)
1 | /** |
这种懒汉单例模式是线程安全的,但由于锁在方法上,所有的访问都需要锁占用,会导致资源的浪费。非特殊情况,不建议使用此种方式来实现单例模式。
3. 饿汉单例模式(线程安全)
1 | /** |
饿汉单例模式并不是懒加载,简单来说就是无论是否用到该类都会在程序启动之初创建。这种方式会导致的问题类似一打开某个软件,手机直接卡死(开启了过多无用功能,导致内存不足)。
4. 双重校验锁单例模式(线程安全)
1 | /** |
双重校验锁方式实现的单例模式是方法级锁的优化,减少了部分获取实例的耗时,同时也满足了懒加载。
5. 静态内部类单例模式(线程安全)
1 | /** |
静态内部类实现的单例模式,既保证了线程安全,也保证了懒加载,同时不会因为加锁的方式导致性能开销过大。
这主要是因为 JVM 虚拟机可以保证多进程并发访问的正确性,即一个类的构造方法在多线程环境下,可以被正确加载。
因此,静态类内部类的实现方式非常推荐使用。
6. CAS「AtomicReference」单例模式(线程安全)
1 | /** |
Java 并发库提供了很多原子类来支持并发访问的数据安全性:AtomicInteger
、 AtomicBoolean
、 AtomicLong
、AtomicReference
。
AtomicReference
可以封装引用一个实例,支持并发访问,该单例方式就是使用该特点实现。
采用 CAS 方式的优点就是不需要传统的加锁方式来保证线程安全,而是依赖 CAS 的忙等算法,依赖底层硬件的实现,来保证线程安全。相对于其他锁的实现没有线程的切换和阻塞,也就没有了额外开销,因此可以支持较大的并发。
当然,CAS 方式也存在缺点,忙等即如果一直没有获取到将会处于死循环中。
7. 枚举单例模式(线程安全)
1 | /** |
约书亚·布洛克(英语:Joshua J. Bloch,1961年8⽉28⽇-),美国著名程序员,《Effective Java》作者。他为Java平台设计并实作了许多的功能,曾担任Google的⾸席Java架构师(Chief Java Architect)。
《Effective Java》作者约书亚·布洛克推荐使用枚举的方式解决单例模式,这种方式应该是平时最少用到的。
这种方式解决的单例模式的主要问题:线程安全、自由串行化、单一实例。
调用方式
1 |
|
这种写法在功能上与共有域⽅法相近,但是它更简洁,⽆偿地提供了串⾏化机制,绝对防⽌对此实例化,即使是在⾯对复杂的串⾏化或者反射攻击的时候。虽然这种方式还没有⼴泛采⽤,但是单元素的枚举类型已经成为实现单例 的最佳⽅法。
但这种⽅式在存在继承场景下是不可⽤的。
四、单例模式结构
- 单例(Singleton)类声明了一个名为
getInstance
获取实例的静态方法来返回其所属类的一个相同实例。 - 单例的构造模式必须对客户端(Client)代码隐藏。调用
获取实例
方法必须是获取单例对象的唯一方式。
设计模式并不难学,其本身就是多年经验提炼出的开发指导思想,关键在于多加练习,带着使用设计模式的思想去优化代码,就能构建出更合理的代码。
源码地址:https://github.com/yiyufxst/design-pattern-java
参考资料:
小博哥重学设计模式:https://github.com/fuzhengwei/itstack-demo-design
深入设计模式:https://refactoringguru.cn/design-patterns/catalog