枚举是如何实现的?(枚举的线程安全性及序列化问题)

Posted more-world

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了枚举是如何实现的?(枚举的线程安全性及序列化问题)相关的知识,希望对你有一定的参考价值。

 

枚举是如何实现的?(枚举的线程安全性及序列化问题)
 
  • 枚举是如何保证线程安全的
    • 举例源码
      1 public enum t {
      2     SPRING,SUMMER,AUTUMN,WINTER;
      3 }
    • 反编译源码
       1 public final class T extends Enum
       2 {
       3     private T(String s, int i)
       4     {
       5         super(s, i);
       6     }
       7     public static T[] values()
       8     {
       9         T at[];
      10         int i;
      11         T at1[];
      12         System.arraycopy(at = ENUM$VALUES, 0, at1 = new T[i = at.length], 0, i);
      13         return at1;
      14     }
      15     public static T valueOf(String s)
      16     {
      17         return (T)Enum.valueOf(demo/T, s);
      18     }
      19     public static final T SPRING;
      20     public static final T SUMMER;
      21     public static final T AUTUMN;
      22     public static final T WINTER;
      23     private static final T ENUM$VALUES[];
      24     static
      25     {
      26         SPRING = new T("SPRING", 0);
      27         SUMMER = new T("SUMMER", 1);
      28         AUTUMN = new T("AUTUMN", 2);
      29         WINTER = new T("WINTER", 3);
      30         ENUM$VALUES = (new T[] {
      31             SPRING, SUMMER, AUTUMN, WINTER
      32         });
      33     }
      34 }
    • 通过反编译后代码我们可以看到,public final class T extends Enum,说明,该类是继承了Enum类的,同时final关键字告诉我们,这个类也是不能被继承的。当我们使用enmu来定义一个枚举类型的时候,编译器会自动帮我们创建一个final类型的类继承Enum类,所以枚举类型不能被继承,我们看到这个类中有几个属性和方法。
    • 我们可以看到:
       1         public static final T SPRING;
       2         public static final T SUMMER;
       3         public static final T AUTUMN;
       4         public static final T WINTER;
       5         private static final T ENUM$VALUES[];
       6         static
       7         {
       8             SPRING = new T("SPRING", 0);
       9             SUMMER = new T("SUMMER", 1);
      10             AUTUMN = new T("AUTUMN", 2);
      11             WINTER = new T("WINTER", 3);
      12             ENUM$VALUES = (new T[] {
      13                 SPRING, SUMMER, AUTUMN, WINTER
      14             });
      15         }
    • 都是static类型的,因为static类型的属性会在类被加载之后被初始化。当一个Java类第一次被真正使用到的时候静态资源被初始化、Java类的加载和初始化过程都是线程安全的。所以,创建一个enum类型是线程安全的。
  • 为什么用枚举实现的单例是最好的方式
    • 1. 枚举写法简单
      1 public enum EasySingleton{
      2     INSTANCE;
      3 }
    • 2. 枚举自己处理序列化
      • 我们知道,以前的所有的单例模式都有一个比较大的问题,就是一旦实现了Serializable接口之后,就不再是单例得了,因为,每次调用 readObject()方法返回的都是一个新创建出来的对象,有一种解决办法就是使用readResolve()方法来避免此事发生。但是,为了保证枚举类型像Java规范中所说的那样,每一个枚举类型极其定义的枚举变量在JVM中都是唯一的,在枚举类型的序列化和反序列化上,Java做了特殊的规定。大概意思就是说,在序列化的时候Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象。同时,编译器是不允许任何对这种序列化机制的定制的,因此禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法。
      • 我们看一下这个valueOf方法:
        1 public static <T extends Enum<T>> T valueOf(Class<T> enumType,String name) {  
        2             T result = enumType.enumConstantDirectory().get(name);  
        3             if (result != null)  
        4                 return result;  
        5             if (name == null)  
        6                 throw new NullPointerException("Name is null");  
        7             throw new IllegalArgumentException(  
        8                 "No enum const " + enumType +"." + name);  
        9         } 
      • 从代码中可以看到,代码会尝试从调用enumType这个Class对象的enumConstantDirectory()方法返回的map中获取名字为name的枚举对象,如果不存在就会抛出异常。再进一步跟到enumConstantDirectory()方法,就会发现到最后会以反射的方式调用enumType这个类型的values()静态方法,也就是上面我们看到的编译器为我们创建的那个方法,然后用返回结果填充enumType这个Class对象中的enumConstantDirectory属性。所以,JVM对序列化有保证。
    • 3.枚举实例创建是thread-safe(线程安全的)
      • 当一个Java类第一次被真正使用到的时候静态资源被初始化、Java类的加载和初始化过程都是线程安全的。所以,创建一个enum类型是线程安全的。
  • 参考http://www.hollischuang.com/archives/197

 






以上是关于枚举是如何实现的?(枚举的线程安全性及序列化问题)的主要内容,如果未能解决你的问题,请参考以下文章

枚举方式的线程安全的单例

单例的线程安全及序列化问题

如何实现单例模式

java里的单例实现

Java并发:五种线程安全类型线程安全的实现枚举类型

是枚举?还是常量?其实很好选择!