概念
单例设计模式(Singleton Design Pattern) 是一种常见的设计模式,属于创建型设计模式,理解起来也非常简单。一个类只允许创建一个对象,那这个类就是一个单例类,这种设计模式就叫做单例模式。
特点:构造方法私有,提供一个全局访问点。
如何实现
1.饿汉式
饿汉式,instance 静态实例在类加载的时候就已经创建并初始化好了,所以,instance 实例的创建过程是线程安全的。
/**
* @Author:
* @Description: 饿汉式 线程安全
*/
public class SingletonExample {
private static final SingletonExample instance = new SingletonExample();
private SingletonExample (){} // 构造方法私有化
public static SingletonExample getInstance() {
return instance;
}
}
这种实现方式不支持延迟加载(在真正使用到 instance 的时候,再创建实例),有人可能会觉得提前初始化好比较浪费资源,觉得在使用的时候再去初始化,但其实问题不大,提前初始化好耗时肯定要好过使用的时候再初始化耗时,避免使用的时候初始化导致的性能问题。
2. 懒汉式
懒汉式,instance 实例在使用的时候才会去初始化,是支持延时加载的,但却是线程不安全的。
/**
* @Author:
* @Description: 懒汉式 线程不安全
*/
public class SingletonExample {
private static SingletonExample instance;
private SingletonExample (){} // 构造方法私有化
public static SingletonExample getInstance() {
if (instance == null){
instance = new SingletonExample();
}
return instance;
}
}
在并发情况下,可能会创建出多个 instance 实例,一个线程进入了 if 条件中,还没有创建好的时候,时间片给了另一个线程,另一个线程此时也会进入 if 条件中。
我们可以给 getInstance() 这个方法加上一把锁,这样的话就变成线程安全的了。
/**
* @Author:
* @Description: 懒汉式 线程安全
*/
public class SingletonExample {
private static SingletonExample instance;
private SingletonExample (){} // 构造方法私有化
public static synchronized SingletonExample getInstance() {
if (instance == null){
instance = new SingletonExample();
}
return instance;
}
}
但加锁的缺点也很明显,加锁之后这个方法的并发度很低,如果这个单例类偶尔才会被用到,那问题不大,但是,如果这个单例类经常被用到,那么频繁的加锁、释放锁也会造成不小的系统开销。
3. 双重检测
饿汉式不支持延时加载,懒汉式不支持高并发,加锁又有性能问题,双重检测实现方式既支持延迟加载、又支持高并发。
/**
* @Author:
* @Description: 双重检测 线程安全
*/
public class SingletonExample {
private static SingletonExample instance;
private SingletonExample (){} // 构造方法私有化
public static SingletonExample getInstance() {
if (instance == null){
// 此处为类级别的锁
synchronized (SingletonExample.class){
if (instance == null){
instance = new SingletonExample();
}
}
}
return instance;
}
}
有人说这种实现方式也有问题,因为可能会发生指令重排序
当执行 instance = new SingletonExample() 这个一步的时候,实际上 cpu 会执行如下指令:
- memory = allocate() 分配对象的内存空间
- ctorInstance() 初始化对象
- instance = memory 设置 instance 指向刚分配的内存
在多线程环境下可能会出现问题,SingletonExample 对象被 new 出来,并且赋值给 instance 之后,还没来得及初始化,就被另一个线程使用了。这样就会出问题。
要解决这个问题,我们需要给 instance 成员变量加上 volatile 关键字,禁止指令重排序。
private static volatile SingletonExample instance;
但其实只有低版本的 Java 才会有这个问题,我们现在用的高版本的 Java 已经在 JDK 内部解决了这个问题。
4.静态内部类
静态内部类的实现方式类似于饿汉式,但又支持延迟加载。
/**
* @Author:
* @Description: 静态内部类 线程安全
*/
public class SingletonExample {
private static volatile SingletonExample instance;
private SingletonExample (){} // 构造方法私有化
private static class SingletonHolder {
private static final SingletonExample instance = new SingletonExample();
}
public static synchronized SingletonExample getInstance() {
return SingletonHolder.instance;
}
}
SingletonHolder 是一个静态内部类,只有在调用 getInstance() 方法时,SingletonHolder 才会被加载、创建 instance,并且是线程安全的。
5. 枚举
枚举类型本身的特性,保证了实例创建的线程安全性和实例的唯一性。
/**
* @Author:
* @Description: 枚举 线程安全
*/
public class SingletonExample {
private SingletonExample (){} // 构造方法私有化
public static SingletonExample getInstance() {
return Singleton.INSTANCE.getInstance();
}
private enum Singleton {
INSTANCE;
private SingletonExample instance;
// JVM 保证这个方法绝对只调用一次
Singleton() {
instance = new SingletonExample();
}
public SingletonExample getInstance() {
return instance;
}
}
}