Singleton 单例,表示仅仅被实例化一次的类。将构造器私有化是实现 Singleton 的最常见方法,将类的构造器私有化,在类的内部实例化一个对象,将对象的域公开发布出去,从而保证了对象的全局唯一性。基于此,实现单例模式的几种方式如下:
一、类加载时就进行唯一实例的初始化,即所谓饿汉式
此种情况由于对象在加载类时即被初始化,因此可以使用以下两种方式将对象暴露给类外
① 静态 final 域 Singleton.INSTANCE 是公有的
public class Singleton {
public static final Singleton INSTANCE = new Singleton();
private Singleton () {}
}
② 静态 final 域 Singleton.INSTANCE 是私有的,通过公有静态方法 getInstance() 发布。
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton () {}
public static Singleton getInstance() {
return INSTANCE;
}
}
二、懒加载,即所谓懒汉式
当单例对象的成员较多,占用内存较大时,或给初始化启动带来较大压力,因此将单例对象的构造推迟至第一次使用该对象时,简单版本如下:
public class Singleton {
private static final Singleton INSTANCE;
private Singleton () {}
public static Singleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
上述方法只有在调用 getInstance() 方法时,才会进行单例的初始化构造,并通过空值判断保证单一实例。
但是在多线程时,由于并发问题导致可能不同线程返回不同实例,因此需要做并发改善
public class Singleton {
private static final Singleton INSTANCE;
private Singleton () {}
public synchronized static Singleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
或
public class Singleton {
private static final Singleton INSTANCE;
private Singleton () {}
public static Singleton getInstance() {
synchronized(Singleton.class) {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
}
上述通过 synchronized 关键字做了同步处理,但线程每次访问获取实例时都需要竞争锁,导致获取实例的过程实际变成了串行,但其实并发问题只存在于第一次进行实例的初始化过程,因此做如下改进
public class Singleton {
// volatile 关键字禁止指令重排序,防止出现空指针异常
// 异常产生原因:
// 由于 new 关键字在 JVM 中并不是原子操作,可分为三步
// 1 分配内存
// 2 构造初始化
// 3 引用赋值
// 在指向操作时,可通过指令重排序进行优化,2 和 3 进行重排
// 当某线程执行完 1 时,指令重排先执行了 3,时间片用完,线程切换
// 此时若发生并发,另一线程在判断 INSTANCE == null 时为 false,返回了
// 还未执行构造的 INSTANCE 引用,后续对 INSTANCE 对象的成员进行访问时,
// 可能会出现空值与空指针的问题
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;
}
}
三、静态内部类
public class Singleton {
private Singleton() {}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
通过使用静态内部类的特性,作为类的成员,只有当调用 getInstance() 方法时,才会触发 SingletonHolder 内部类的类加载过程,进而确保了线程安全与懒加载。
注意:到此,上述的方法中都存在两个隐含问题导致有可能破坏单例
- 通过反射强制调用构造方法
- 由于对象序列化与反序列化导致的单例模式破坏
解决方案:
对于反射,可以在私有构造器中抛出异常避免。
private static volatile boolean flag = true;
private Singleton() {
if (flag ) {
flag = false;
} else {
throw new RuntimeException("重复对象实例化错误");
}
}
补充:对于一些永远不需要实例化的工具类,可以直接使用
private Singleton() {
throw new RuntimeException("重复对象实例化错误");
}
对于序列化问题,需要在类中添加如下方法:
private Object readResolve() throws ObjectStreamException {
return INSTANCE;
}
四、枚举
枚举方式更加简洁、并无偿提供了序列化机制,使用单元素的枚举类型经常成为实现 Singleton 的最佳方法
public enum Singleton {
INSTANCE;
}