单例设计模式
# 定义
单例设计模式(Singleton Design Pattern)是一种创建型设计模式。它确保一个类只有一个实例,并提供一个全局访问点来访问这个实例。就好像在一个软件系统中,对于某些特殊的资源或者对象,我们只希望它有一个,例如系统的配置信息管理类、数据库连接池等。
# 核心思想
- 确保类只有一个实例。
- 提供一个全局访问点来获取这个实例。
# 目的
- 控制资源的使用,通过共享唯一的实例来优化系统性能。
- 避免频繁创建和销毁对象的开销。
# 实现方式
- 私有构造方法,防止外部实例化。
- 静态私有成员变量,存储唯一实例。
- 静态公共方法,提供全局访问点。
# 常见实现方式
# 示例
# 饿汉式(静态常量)
class Singleton {
// 直接创建对象实例,并且通过private static final修饰
private static final Singleton instance = new Singleton();
// 构造方法私有化,防止外部通过new创建实例
private Singleton() {}
// 提供一个公共的静态方法返回实例
public static Singleton getInstance() {
return instance;
}
}
2
3
4
5
6
7
8
9
10
这种方式在类加载的时候就创建了单例对象。因为static
变量在类加载阶段初始化,并且final
关键字保证了这个引用不可变。由于类加载过程是由类加载器来保证同步的,所以在多线程环境下是安全的。它的优点是实现简单,并且在多线程环境下安全可靠。缺点是可能会造成资源的浪费,因为如果这个单例对象一直没有被使用,但是在类加载的时候就已经创建了。
# 饿汉式(静态代码块)
class Singleton {
private static Singleton instance;
static {
// 在静态代码块中创建实例
instance = new Singleton();
}
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
2
3
4
5
6
7
8
9
10
11
与饿汉式(静态常量)类似,在类加载时执行静态代码块,创建单例对象。在多线程环境下也是安全的,因为类加载过程是线程安全的。不过同样存在资源可能被浪费的问题,即如果单例对象一直未被使用,却在类加载时就已经创建。
# 懒汉式(线程不安全)
class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
// 如果实例不存在,则创建实例
instance = new Singleton();
}
return instance;
}
}
2
3
4
5
6
7
8
9
10
11
这种方式是在第一次调用getInstance
方法时才创建实例。但是在多线程环境下是不安全的。例如,当两个线程同时调用getInstance
方法,并且都判断instance
为null
时,会创建两个不同的实例,这就违背了单例模式的初衷。不过这种方式在单线程环境下或者对实例创建时机要求比较灵活的情况下可以使用,因为它实现了延迟加载,只有在真正需要实例的时候才会创建。
# 懒汉式(线程安全,同步方法)
class Singleton {
private static Singleton instance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
2
3
4
5
6
7
8
9
10
在getInstance
方法上添加了synchronized
关键字,这使得该方法在多线程环境下是线程安全的。当多个线程访问getInstance
方法时,会排队依次执行。不过这种方式的性能较差,因为每次调用getInstance
方法都需要获取锁,即使实例已经创建,也需要等待锁的释放,会增加额外的开销。
# 懒汉式(线程安全,同步代码块)
class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
这种方式是对懒汉式进行了优化。首先判断instance
是否为null
,如果为null
,再进入同步代码块。在同步代码块中再次判断instance
是否为null
,这样可以避免在实例已经创建后,每次获取实例都需要获取锁的开销。不过这种方式的实现相对复杂一些,而且如果在多线程环境下,第一次创建实例时,多个线程同时判断instance
为null
,还是会有多个线程竞争锁的情况。
# 双重检查
class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
与懒汉式(线程安全,同步代码块)类似,但是这里添加了volatile
关键字来修饰instance
变量。这是因为在instance = new Singleton();
这一操作不是原子操作,它包含了分配内存空间、初始化对象、将对象引用赋值给instance
三个步骤。在没有volatile
关键字的情况下,可能会出现一个线程已经分配了内存空间并初始化了对象,但是还没有将对象引用赋值给instance
,另一个线程就判断instance
为null
,从而又创建一个新的对象。volatile
关键字保证了变量的可见性,使得其他线程能够及时看到instance
的最新状态。
# 静态内部类
class Singleton {
private Singleton() {}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
2
3
4
5
6
7
8
9
当外部类Singleton
被加载时,内部类SingletonHolder
不会被加载。只有当调用getInstance
方法时,才会加载SingletonHolder
类,从而创建单例对象INSTANCE
。这种方式既保证了线程安全(因为类加载是线程安全的),又实现了懒加载,避免了资源的浪费。它结合了懒汉式和饿汉式的优点,是一种比较推荐的单例模式实现方式。
# 枚举
enum Singleton {
INSTANCE;
public void doSomething() {
// 可以在这里定义单例对象的方法
}
}
2
3
4
5
6
利用枚举类型的特性来实现单例模式。在 Java 中,枚举类型的实例是唯一的,并且枚举的构造方法默认是私有的,这就保证了只能有一个实例存在。枚举类型还具有线程安全、防止反序列化重新创建对象等优点。使用枚举实现单例模式非常简洁,而且在功能上更加健壮,是一种比较优雅的实现方式。