一、饿汉式
在 Main 这个类被加载的时候,singleton 静态实例就已经被创建并初始化好了,因此饿汉式单例一定是线程安全的。
public class Main {
private static final Object singleton = new Object();
public static Object getSingleton() {
return singleton;
}
}
2. 懒汉式(双重检测锁)
双重检测锁介绍的文章有很多,这里就不赘述了。比较值得关注的是,在早期 JDK 中需要对 singleton 声明为 volatile 以禁用对象创建动作的指令重排。
若不声明为 volatile,在指令重排后,new Object 这个操作会先分配内存地址,再将引用指向内存地址,最后才是在内存地址上创建实例。这就可能导致,线程 A 还未在内存地址上创建实例,线程 B 就调用 getSingleton() 获取到实例并使用了,这就会导致空指针异常。
这个问题具体是在哪个版本被解决我也不太清楚,总之 JDK1.8 往后都不会有这种问题。
public class Main {
private static Object singleton;
public static Object getSingleton() {
if (singleton == null) {
synchronized (Main.class) {
if (singleton == null) {
singleton = new Object();
}
}
}
return singleton;
}
}
3. 静态内部类(推荐)
静态内部类 SingletonHolder 并不会在 Main 被加载时就被创建,只有当调用了 getSingleton() 方法时 SingletonHolder 才会被加载,这是与饿汉式单例最大的区别。
public class Main {
private static class SingletonHolder {
private static final Object singleton = new Object();
}
private static Object getSingleton() {
return SingletonHolder.singleton;
}
}
4. 枚举
基于 Java 枚举的特性,保证了实例创建的线程安全以及唯一性。这种方式也是比较简单且推荐使用的。
public enum Main {
INSTANCE;
private Object singleton = new Object();
public Object getSingleton() {
return singleton;
}
}