深度分析Java的枚举类型—-枚举的线程安全性及序列化问题

原文:深度分析Java的枚举类型—-枚举的线程安全性及序列化问题html

枚举是如何保证线程安全的

要想看源码,首先得有一个类吧,那么枚举类型究竟是什么类呢?是enum吗?答案很明显不是,enum就和class同样,只是一个关键字,他并非一个类,那么枚举是由什么类维护的呢,咱们简单的写一个枚举:java

public enum t {
    SPRING,SUMMER,AUTUMN,WINTER;
}

而后咱们使用反编译,看看这段代码究竟是怎么实现的,反编译(Java的反编译)后代码内容以下:设计模式

public final class T extends Enum
{
    private T(String s, int i)
    {
        super(s, i);
    }
    public static T[] values()
    {
        T at[];
        int i;
        T at1[];
        System.arraycopy(at = ENUM$VALUES, 0, at1 = new T[i = at.length], 0, i);
        return at1;
    }

    public static T valueOf(String s)
    {
        return (T)Enum.valueOf(demo/T, s);
    }

    public static final T SPRING;
    public static final T SUMMER;
    public static final T AUTUMN;
    public static final T WINTER;
    private static final T ENUM$VALUES[];
    static
    {
        SPRING = new T("SPRING", 0);
        SUMMER = new T("SUMMER", 1);
        AUTUMN = new T("AUTUMN", 2);
        WINTER = new T("WINTER", 3);
        ENUM$VALUES = (new T[] {
            SPRING, SUMMER, AUTUMN, WINTER
        });
    }
}

经过反编译后代码咱们能够看到,public final class T extends Enum,说明,该类是继承了Enum类的,同时final关键字告诉咱们,这个类也是不能被继承的。当咱们使用enum来定义一个枚举类型的时候,编译器会自动帮咱们建立一个final类型的类继承Enum类,因此枚举类型不能被继承,咱们看到这个类中有几个属性和方法。安全

咱们能够看到:app

public static final T SPRING;
public static final T SUMMER;
public static final T AUTUMN;
public static final T WINTER;
private static final T ENUM$VALUES[];
static
{
    SPRING = new T("SPRING", 0);
    SUMMER = new T("SUMMER", 1);
    AUTUMN = new T("AUTUMN", 2);
    WINTER = new T("WINTER", 3);
    ENUM$VALUES = (new T[] {
        SPRING, SUMMER, AUTUMN, WINTER
    });
}

都是static类型的,由于static类型的属性会在类被加载以后被初始化,咱们在深度分析Java的ClassLoader机制(源码级别)Java类的加载、连接和初始化两个文章中分别介绍过,当一个Java类第一次被真正使用到的时候静态资源被初始化、Java类的加载和初始化过程都是线程安全的。因此,建立一个enum类型是线程安全的。线程

为何用枚举实现的单例是最好的方式

【设计模式】【创造型模式】单例模式中,咱们看到一共有六种实现单例的方式,其中,Effective Java,做者,Josh Bloch, 提倡使用枚举的方式,既然大神说这种方式好,那咱们就要知道它为何好?设计

1. 枚举写法简单code

写法简单这个你们看看【设计模式】【创造型模式】单例模式里面的实现就知道区别了。orm

public enum EasySingleton{
    INSTANCE;
}

你能够经过EasySingleton.INSTANCE来访问。htm

2. 枚举本身处理序列化

咱们知道,之前的全部的单例模式都有一个比较大的问题,就是一旦实现了Serializable接口以后,就再也不是单例得了,由于,每次调用 readObject()方法返回的都是一个新建立出来的对象,有一种解决办法就是使用readResolve()方法来避免此事发生。可是,为了保证枚举类型像Java规范中所说的那样,每个枚举类型极其定义的枚举变量在JVM中都是惟一的,在枚举类型的序列化和反序列化上,Java作了特殊的规定。原文以下:

Enum constants are serialized differently than ordinary serializable or externalizable objects. 
The serialized form of an enum constant consists solely of its name; 
field values of the constant are not present in the form. 
To serialize an enum constant, ObjectOutputStream writes the value returned by the enum constant’s name method. 
To deserialize an enum constant, ObjectInputStream reads the constant name from the stream;
 the deserialized constant is then obtained by calling the java.lang.Enum.valueOf method,
 passing the constant’s enum type along with the received constant name as arguments. 
Like other serializable or externalizable objects, enum constants can function as the targets of back references appearing subsequently in the serialization stream.
 The process by which enum constants are serialized cannot be customized: any class-specific writeObject, readObject, readObjectNoData, writeReplace, 
and readResolve methods defined by enum types are ignored during serialization and deserialization. Similarly, any serialPersistentFields or 
serialVersionUID field declarations are also ignored–all enum types have a fixedserialVersionUID of 0L. 
Documenting serializable fields and data for enum types is unnecessary, since there is no variation in the type of data sent.

大概意思就是说,在序列化的时候Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是经过java.lang.EnumvalueOf方法来根据名字查找枚举对象。同时,编译器是不容许任何对这种序列化机制的定制的,所以禁用了writeObject、readObject、readObjectNoData、writeReplacereadResolve等方法。 咱们看一下这个valueOf方法:

public static <T extends Enum<T>> T valueOf(Class<T> enumType,String name) {  
            T result = enumType.enumConstantDirectory().get(name);  
            if (result != null)  
                return result;  
            if (name == null)  
                throw new NullPointerException("Name is null");  
            throw new IllegalArgumentException(  
                "No enum const " + enumType +"." + name);  
        }

从代码中能够看到,代码会尝试从调用enumType这个Class对象的enumConstantDirectory()方法返回的map中获取名字为name的枚举对象,若是不存在就会抛出异常。再进一步跟到enumConstantDirectory()方法,就会发现到最后会以反射的方式调用enumType这个类型的values()静态方法,也就是上面咱们看到的编译器为咱们建立的那个方法,而后用返回结果填充enumType这个Class对象中的enumConstantDirectory属性。

因此,JVM对序列化有保证。

3.枚举实例建立是thread-safe(线程安全的)

咱们在深度分析Java的ClassLoader机制(源码级别)Java类的加载、连接和初始化两个文章中分别介绍过,当一个Java类第一次被真正使用到的时候静态资源被初始化、Java类的加载和初始化过程都是线程安全的。因此,建立一个enum类型是线程安全的。

相关文章
相关标签/搜索