常见的五种单例模式实现方式– 主要:
- 饿汉式(线程安全,调用效率高。 但是,不能延时加载。)
- 懒汉式(线程安全,调用效率不高。 但是,可以延时加载。)
-
其他:
- 双重检测锁式(由于JVM底层内部模型原因,偶尔会出问题。不建议使用)
- 静态内部类式(线程安全,调用效率高。 但是,可以延时加载)
- 枚举式(线程安全,调用效率高,不能延时加载。并且可以天然的防止反射和反序列化漏洞!)
-
如何选用?
-
单例对象 占用 资源 少,不需要 延时加载
- 枚举式 好于 饿汉式
-
单例对象 占用 资源 大,需要 延时加载:
- 静态内部类式 好于 懒
-
饿汉
public class OneObject {
private static final OneObject OBJECT = new OneObject();
private OneObject () {}
public static OneObject build () {
return OBJECT;
}
}
懒汉
public class OneObjectLaz {
private static OneObjectLaz laz;
private OneObjectLaz () {}
public static OneObjectLaz build () {
synchronized (OneObjectLaz.class) {
if (null == laz) laz = new OneObjectLaz();
}
return laz;
}
}
多线程测试
for (int i = 0; i < 10; i++) {
new Thread(() -> {
OneObjectLaz build = OneObjectLaz.build();
System.out.println(build.hashCode());
}).start();
}
通过反射机制暴力范围 private 构造器, 从而达到多实例
Constructor<OneObject> constructor = OneObject.class.getDeclaredConstructor();
constructor.setAccessible(true);
OneObject newInstance = constructor.newInstance();
System.out.println(newInstance);
解决方案
public class OneObjectLaz {
private static OneObjectLaz laz;
private OneObjectLaz () {
if (laz != null) throw new RuntimeException("对象必须保证单例!");
}
public static OneObjectLaz build () {
synchronized (OneObjectLaz.class) {
if (null == laz) laz = new OneObjectLaz();
}
return laz;
}
}
防止序列化破解单例
public class OneObjectLaz {
private static OneObjectLaz laz;
private OneObjectLaz () {
if (laz != null) throw new RuntimeException("对象必须保证单例!");
}
public static OneObjectLaz build () {
synchronized (OneObjectLaz.class) {
if (null == laz) laz = new OneObjectLaz();
}
return laz;
}
/**
* 防止单例, 反序列化破解
* 如果定义了: readResolve 方法在反序列时直接返回方法指定的对象, 不需要new了
*/
private Object readResolve() throws ObjectStreamException {
return laz;
}
}
要想真正的实现单利漏洞还是很多的所以
jdk
提供了新特性:enum
public enum One {
ONE, TWO, THREE;
}