前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >为什么说“单元素的枚举类型已经成为实现Singleton的最佳方法”

为什么说“单元素的枚举类型已经成为实现Singleton的最佳方法”

作者头像
程序员小强
修改2019-08-07 16:43:43
1.2K0
修改2019-08-07 16:43:43
举报

预计阅读时间:10分钟

Joshua Bloch大神在《Effective Java》中明确表达过的观点:使用枚举实现单例的方法虽然还没有广泛采用,但是单元素的枚举类型已经成为实现Singleton的最佳方法。why?

咱们先看看常规写法:

① 饿汉模式1

代码语言:javascript
复制
public class Singleton {
     private static Singleton instance = new Singleton();
     private Singleton (){}
     public static Singleton getInstance() {
         return instance;
     }
}

② 饿汉模式2

代码语言:javascript
复制
public class Singleton {
     private static Singleton instance = null;
     static {
         instance = new Singleton();
     }
     private Singleton (){}
     public static Singleton getInstance() {
         return instance;
     }
}

③ 懒汉模式

代码语言:javascript
复制
public class Singleton {
     private static Singleton instance;
     private Singleton (){}
     public static synchronized Singleton getInstance() {
         if (instance == null) {
             instance = new Singleton();
         }
         return instance;
     }
}

④ 内部类方式

代码语言:javascript
复制
public class Singleton {
     private static class SingletonHolder {
         private static final Singleton INSTANCE = new Singleton();
     }
     private Singleton (){}
     public static final Singleton getInstance() {
         return SingletonHolder.INSTANCE;
     }
}

⑤ Double-Check

代码语言:javascript
复制
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;
     }
}

以上是比较多的单例的书写方式,但是以上书写方式可以通过反射的方式修改实例对象,如:

代码语言:javascript
复制
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》中明确支持此种方式,如下:

代码语言:javascript
复制
public enum Singleton {
     INSTANCE;
     public void whateverMethod() {
     }
}

简单不?不止简单,还成功避免了上述问题,而且能保证在反序列化的时候不会生成新的实例对象,以下是枚举方式反编译(使用javap命令)的字节码:

代码语言:javascript
复制
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属性的例子

代码语言:javascript
复制
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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-06-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 MoziInnovations 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • ⑤ Double-Check
相关产品与服务
文件存储
文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档