public class Hungry {
public final static Hungry INSTANCE = new Hungry();
private Hungry() {
}
}
public class HungryStatic {
public final static HungryStatic INSTANCE;
static {
INSTANCE = new HungryStatic("例如从配置文件读入");
}
private HungryStatic(String config) {
}
private String config;
public String getConfig() {
return config;
}
public void setConfig(String config) {
this.config = config;
}
}
public enum Enum {
INSTANCE;
}
在调用的时候才创建
public class Lazy {
private static Lazy instance;
private Lazy() {
}
public static Lazy getInstance() {
if (instance == null) {
instance = new Lazy();
}
return instance;
}
}
public class LazySafe {
private static volatile LazySafe instance;
private LazySafe() {
}
public static LazySafe getInstance() {
if (instance == null) {
synchronized (LazySafe.class) {
if (instance == null) {
instance = new LazySafe();
}
}
}
return instance;
}
}
因为 如果两个线程 同时判断 instance == null ,开始争抢锁,第一个线程执行过后,如果不加锁,线程二会直接new 一个新对象,所以必须加锁保证 。
jvm 会将 new 指令解析为下面的语句。
由于指令会重排序,所以 7 有可能 发生在 4之前,也就是 先分配指针指向对象,但是对象尚未初始化也就是null的情况,所以添加关键字禁止重排序情况的发生。
原理 : 静态内部类的延迟初始化,且类只会加载一次。
public class LazyInner {
private LazyInner() {
}
private static class Inner {
private static final LazyInner INSTANCE = new LazyInner();
}
public static LazyInner getInstance() {
return Inner.INSTANCE;
}
}
The “Double-Checked Locking is Broken” Declaration
懒汉式创建
在 new 对象的过程中,如果不添加 volatile 关键字,可能会出现重排序,也就是 对象初始化发生在 引用指向 之前,在这一段时间内,其他线程会拿到为null的对象。
我们以Runtime为例来演示使用反射破坏单例。
Class<Runtime> aClass = Runtime.class;
Constructor<Runtime> cons = aClass.getDeclaredConstructor();
cons.setAccessible(true);
Runtime runtime = cons.newInstance();
Runtime runtime2 = cons.newInstance();
System.out.println(runtime);
System.out.println(runtime2);
----
java.lang.Runtime@568db2f2
java.lang.Runtime@378bf509
优先使用枚举来解决反射破坏单例的问题
我们来获取一下枚举类中的构造器
Class<Enum> aClass = Enum.class;
Constructor<?>[] constructors = aClass.getDeclaredConstructors();
// Constructor<Enum> cons = aClass.getDeclaredConstructor();
Constructor<Enum> cons2 = aClass.getDeclaredConstructor(String.class, int.class);
// cons.setAccessible(true);
cons2.setAccessible(true);
// Enum e1 = cons.newInstance();
// Enum e2 = cons.newInstance();
Enum e3 = cons2.newInstance();
Enum e4 = cons2.newInstance();
可以看到里面会生成一个String , int 参数类型的构造器
idea 反编译结果跟预期明显不同。
javap 反编译 class 文件后发现也没有相应的构造方法
我们更换反编译工具,使用 jad 进行后续的测试
将 jad.exe 放在 jdk 的 bin 目录下即可
jad 反编译后成功看到Enum类中 String 和 int 参数的构造函数。
Runtime类是 饿汉式 单例