预计阅读时间:10分钟
Joshua Bloch大神在《Effective Java》中明确表达过的观点:使用枚举实现单例的方法虽然还没有广泛采用,但是单元素的枚举类型已经成为实现Singleton的最佳方法。why?
咱们先看看常规写法:
① 饿汉模式1
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
② 饿汉模式2
public class Singleton {
private static Singleton instance = null;
static {
instance = new Singleton();
}
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
③ 懒汉模式
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
④ 内部类方式
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
以上是比较多的单例的书写方式,但是以上书写方式可以通过反射的方式修改实例对象,如:
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
public static void main(String[] args) throws Exception{
System.out.println(Singleton.getSingleton());
System.out.println(Singleton.getSingleton());
Field field = Singleton.class.getDeclaredField("singleton");
field.setAccessible(true);
Constructor<?> constructor = Singleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
Object instance = constructor.newInstance();
field.set(Singleton.class, instance);
System.out.println(Singleton.getSingleton());
System.out.println(Singleton.getSingleton());
}
}
以下是上述main方法输出:
如上图所示,使用Double-Check方式书写的单例实例对象被成功修改,以上的几种方式都存在这种问题,那么有没有一种方式不存在上述问题呢?有,可以使用枚举的方式,Joshua Bloch大神也在《Effective Java》中明确支持此种方式,如下:
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
简单不?不止简单,还成功避免了上述问题,而且能保证在反序列化的时候不会生成新的实例对象,以下是枚举方式反编译(使用javap命令)的字节码:
javap Singleton.class
Compiled from "Singleton.java"
public final class com.test.Singleton extends java.lang.Enum<com.test.Singleton> {
public static final com.test.Singleton INSTANCE;
public static com.test.Singleton[] values();
public static com.test.Singleton valueOf(java.lang.String);
public void whateverMethod();
static {};
}
以下是通过反射修改Singleton对象的INSTANCE属性的例子
public enum Singleton {
INSTANCE;
public void whateverMethod() {}
public static void main(String[] args) throws Exception{
System.out.println(Singleton.INSTANCE);
System.out.println(Singleton.INSTANCE);
Field field = Singleton.class.getDeclaredField("INSTANCE");
field.setAccessible(true);
Constructor<?> constructor = Singleton.class.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true);
Object instance = constructor.newInstance("", 0);
field.set(Singleton.class, instance);
System.out.println(Singleton.INSTANCE);
System.out.println(Singleton.INSTANCE);
}
}
执行结果:
如上,会报错,而且目前没有任何方法能绕过该限制,以下是jdk的newInstance方法的源代码,如果是ENUM类型则不允许使用反射(红色标注部分),看到这里大家应该明白为什么枚举方式能保证单例实例的安全
这种方式是Effective Java作者Josh Bloch 大神提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,所以极力大家写单例时使用此种方式
End
本文分享自 MoziInnovations 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体分享计划 ,欢迎热爱写作的你一起参与!