深度分析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類型是線程安全的。

相關文章
相關標籤/搜索