饿汉式 线程安全
public class Singleton {
private final static Singleton instance = new Singleton();
private Singleton() {
System.out.println("Constructing Singleton");
}
public static Singleton getInstance() {
return instance;
}
}
- instance 声明为 私有静态 变量, 在类加载时即调用构造函数,因此线程安全
- 缺点:不管是否调用 getInstance 使用实例,都会占用内存空间
Double Checked Locking 线程安全
public class Singleton {
private static Singleton instance;
private Singleton() {
System.out.println("Constructing Singleton");
}
public static Singleton getInstance() {
if(instance == null) {
synchronized (Singleton.class) {
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
注意事项:
- instance 声明为 私有静态 变量
- 构造函数声明为 私有
- getInstace 函数声明为 公有静态 方法
- 可能存在指令重排的问题。
- 解决:用 volatile 修饰 instance
private static volatile Singleton instance;
- volatile修饰的话就可以确保
instance = new Singleton();
对应的指令不会重排序
- 解决:用 volatile 修饰 instance
占位符模式 线程安全
public class Singleton {
// 成员内部类
private static class SingletonHolder {
static Singleton instance = new Singleton();
}
private Singleton() {
System.out.println("Constructing Singleton");
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
}
- 内部类 SingletonHolder 只有在 getInstance() 方法第一次调用的时候才会被加载(实现了lazy,避免了饿汉模式的缺点)
- static 域中修改共享变量是线程安全的,由JVM保障
通过反射机制攻击单例模式
public static void main(String[] args) throws Exception {
Constructor c = Singleton.class.getDeclaredConstructor(null);
c.setAccessible(true);
Singleton s1 = (Singleton)c.newInstance();
Singleton s2 = (Singleton)c.newInstance();
}
以上的代码可以多次调用构造函数,生成多个对象实例。
可以通过修改构造函数抵御这种攻击:
private static boolean flag = true;
// 私有构造函数
private Singleton() {
synchronized (Singleton.class) {
if(flag) {
flag = !flag;
System.out.println("Constructing Singleton");
} else {
throw new RuntimeException("Reject Constructing Singleton");
}
}
}