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


  • 枚举是如何保证线程安全的
  • 举例源码
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类型是线程安全的。