设计模式 单例模式
本文将介绍设计模式中的单例模式。
一、什么是单例模式?
确保一个类只有一个实例,并提供一个全局访问点来访问这个唯一实例。
二、实现
1. 结构
2. 角色
- Singleton 单例:
- 在类的内部持有该类的唯一实例
- 提供静态方法,允许外界获取实例
3. 要点
- 私有化构造函数,不允许外部通过 new 创建实例
- 将实例以静态的、私有的类成员的方式,维护在类中
- 提供一个静态的、共有的实例获取函数
三、单例的写法
1. 饿汉式与懒汉式
- 懒汉式:有事不急着做,等火烧眉毛了再做
- 饿汉式:有事一来就先做了
2. 不安全的懒汉式单例
1 |
|
这个方法实际上是线程不安全的,如果在最开始时有多个线程同时尝试获取该实例,多个线程可能会获取到多个不同实例。
3. 饿汉式单例
1 |
|
在定义静态变量时直接将其实例化,当类的静态方法被调用前,单例就已经创建。
当
Singleton.getInstance()
被执行时,Singleton 类会被加载进虚拟机,静态成员变量会被创建并复制,这个过程会在类加载阶段执行,并且虚拟机会保证其线程安全。因此,在执行
Singleton.getInstance()
之前,实例就已经存在。
4. 懒汉式单例 + synchronized
1 |
|
通过在方法上增加 synchronized 关键字,能够保证同一时刻只有一个线程能够访问此方法,从而避免了上面的线程不安全问题。
但此方法的性能实际上是较差的,因为同一时刻只能有一个线程能够访问该方法,也意味着同一时刻只能有一个线程获取实例。
5. 懒汉式单例 + 细粒度锁
1 |
|
将加锁的粒度缩小至实例化时,不仅可以保证同一时刻只能有一个线程尝试实例化,也不会影响多个线程对实例的获取。
但这一方法存在线程安全问题:假如多个线程同时访问该方法,发现 instance 为 null,它们会尝试加锁、陆续获取到锁、进行实例化,导致系统中出现多个实例。
6. 懒汉式单例 + 细粒度锁 + 双重检查 + volatile
1 |
|
主要进行了两个工作:
- 双重检查:当成功获取到锁后,检查实例状态,避免 “陆续获取到锁” 导致的重复实例化
- 增加 volatile 关键字:禁用 CPU 缓存,使 CPU 读取该变量时从内存中读取
四、优缺点
1. 优点
- 限定了外界对实例的获取入口,因此可以方便地控制外界对实例的获取方式、时机
- 可以节省系统资源
2. 缺点
- 单例类的职责过重,既负责业务功能,也负责提供对象获取
- 现在许多语言都提供了自动垃圾回收功能,如果单例长时间不被利用,它可能会被销毁并回收
参考
- 《Java 设计模式》
- 你真的会写单例模式吗 | Depp Wang’s Blog