文章目录
1 基本介绍
单例模式(Singleton Pattern)是一种 创建型 设计模式,其目的是 确保一个类仅有一个实例,并提供一个 静态方法 来获取该实例。
2 实现方式
单例模式围绕着两个特性展开:
- 延迟加载:在需要这个单例时才创建单例,避免浪费内存。
- 线程安全:在多线程环境下,保证多线程使用的单例是同一个单例。
共有以下六种实现方式:
2.1 饿汉式
2.1.1 代码
饿汉式的实现在 Java 中有两种实现,常用的是第一种。
方式一:显式初始化。
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static synchronized Singleton getInstance() {
return INSTANCE;
}
}
方式二:静态代码块初始化。
public class Singleton {
private static final Singleton INSTANCE;
static {
INSTANCE = new Singleton();
}
private Singleton() {}
public static synchronized Singleton getInstance() {
return INSTANCE;
}
}
2.1.2 特性
- 不延迟加载:在 类加载 时就创建单例。
- 线程安全:类加载由 JVM 保证线程安全,所以此时创建的单例也是线程安全的。
2.2 懒汉式 ( 线程不安全 )
2.2.1 代码
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
2.2.2 特性
- 延迟加载:只有在 调用获取单例的方法
getInstance()
时才创建单例。 - 线程不安全:如果有多个线程同时通过了
singleton == null
这个条件,则它们会创建多个单例。
2.3 懒汉式 ( 线程安全 )
2.3.1 代码
注意 getInstance()
方法前的 synchronized
修饰符。
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
2.3.2 特性
- 延迟加载:只有在 调用获取单例的方法
getInstance()
时才创建单例。 - 线程安全:在多个线程同时获取单例时,使用
synchronized
互斥锁,来保证只有一个线程能够生成单例,而其他线程等待这个线程创建的单例,保证了单例的线程安全。 - 成本太大:即使已经有单例了,每次调用
getInstance()
方法还得经过 加锁 和 释放锁 的流程(因为使用了synchronized
互斥锁),降低了并发性能。
2.4 双重检查
2.4.1 代码
注意单例前的 volatile
修饰符,它有两个作用:保证变量对所有线程可见 和 防止指令重排。在此处起 防止指令重排 的作用:防止 JIT 即时编译器对 instance = new Singleton();
这行代码进行重排序。
如果进行重排序,则可能先给 instance
分配内存(此时 instance != null
),然后才调用构造器为 instance
的属性赋值。在这两步操作之间,要是有线程调用 getInstance()
方法,它将无法通过外层的 instance == null
条件,会返回一个不完整(赋值不完全)的对象。
public class Singleton {
private static volatile Singleton instance; // 注意 volatile 在这里起 防止指令重排 的作用
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
2.4.2 特性
- 延迟加载:只有在 调用获取单例的方法
getInstance()
时才创建单例。 - 线程安全:在多个线程同时获取单例 且 单例未创建时,如果都通过了外层的
instance == null
条件,则在内层使用synchronized
互斥锁,来保证只有一个线程创建单例,而其他线程等待这个线程创建的单例,保证了单例的线程安全。 - 成本小:这种实现方式只有最初创建单例时会加互斥锁,之后就不需要创建单例了,直接返回即可,无需加锁和释放锁,提高了并发性能。
2.5 静态内部类
2.5.1 代码
public class Singleton {
private Singleton() {}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
2.5.2 特性
- 延迟加载:只有在 调用获取单例的方法
getInstance()
时,才会 触发静态内部类的加载,从而创建单例。 - 线程安全:静态内部类也是类,类加载由 JVM 保证其线程安全,所以本单例是线程安全的。
2.6 枚举
2.6.1 代码
public enum Singleton {
INSTANCE; // 直接使用 Singleton.INSTANCE 就可以获取到单例
// 可以随意写方法和属性,就像在类中一样
}
2.6.2 特性
- 不延迟加载:在 类加载 时就创建单例。
- 线程安全:类加载由 JVM 保证线程安全,所以此时创建的单例也是线程安全的。
- 防止 反射 或 反序列化 破坏单例:其他单例的实现都可以通过 反射 或 反序列化 的方式重新创建新的单例,唯独本实现无法使用这两种方式重新创建新的单例,这是因为 枚举无法通过反射获取对象,并且 枚举在序列化和反序列化时不会调用构造器。所以这种实现是 最推荐的。
3 实现的要点
- 构造器私有化。例如
private Singleton() {}
。 - 类中有一个 静态 的单例属性。
- 提供一个 静态 方法来获取上述单例。
4 线程不安全的单例模式
4.1 代码
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
instance = new Singleton();
}
}
return instance;
}
}
4.2 评价
这样的单例模式是 线程不安全 的,synchronized
互斥锁没有起到应有的作用。只要多个线程都能通过 instance == null
条件,则它们每个线程都会创建一次单例,synchronized
仅仅能保证同一时刻只有一个线程在创建单例罢了。
应该将 判断 和 赋值 都放到 synchronized
互斥锁里,就像单例的 第三种实现——懒汉式 ( 线程安全 ) 一样。
5 JDK中的单例模式
在JDK中,Runtime
类使用了 饿汉式单例,代码如下:
public class Runtime {
private static final Runtime currentRuntime = new Runtime();
public static Runtime getRuntime() {
return currentRuntime;
}
private Runtime() {}
// ...
}
6 单例模式的类图及角色
6.1 类图
其中,singleton
属性和 Singleton()
构造器都是 私有的,而 getInstance()
方法是 公开的。此外,singleton
属性和 getInstance()
方法都是 静态的。
6.2 角色
在单例模式中,只有一个角色 Singleton
,它负责 实现返回单例的 静态 方法。
7 推荐的单例模式的实现
- 饿汉式
- 双重检查
- 静态内部类
- 枚举
8 单例模式的使用场景
- 创建对象耗时过多或耗费资源过多(重量级),但经常用到。
- 频繁访问 数据库 或 文件 的对象。
- 工具类对象。