Java中枚舉的線程安全性及序列化問題

2021-02-18 Hollis

Java SE5提供了一種新的類型-Java的枚舉類型,關鍵字enum可以將一組具名的值的有限集合創建為一種新的類型,而這些具名的值可以作為常規的程序組件使用,這是一種非常有用的功能。本文將深入分析枚舉的源碼,看一看枚舉是怎麼實現的,他是如何保證線程安全的,以及為什麼用枚舉實現的單例是最好的方式。

要想看源碼,首先得有一個類吧,那麼枚舉類型到底是什麼類呢?是enum嗎?答案很明顯不是,enum就和class一樣,只是一個關鍵字,他並不是一個類,那麼枚舉是由什麼類維護的呢,我們簡單的寫一個枚舉:

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關鍵字告訴我們,這個類也是不能被繼承的。當我們使用enmu來定義一個枚舉類型的時候,編譯器會自動幫我們創建一個final類型的類繼承Enum類,所以枚舉類型不能被繼承,我們看到這個類中有幾個屬性和方法。

我們可以看到:

        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類型是線程安全的

1. 枚舉寫法簡單

寫法簡單這個大家看看單例模式的七種寫法裡面的實現就知道區別了。

public enum EasySingleton{
    INSTANCE;
}

你可以通過EasySingleton.INSTANCE來訪問。

2. 枚舉自己處理序列化

我們知道,以前的所有的單例模式都有一個比較大的問題,就是一旦實現了Serializable接口之後,就不再是單例得了,因為,每次調用 readObject()方法返回的都是一個新創建出來的對象,有一種解決辦法就是使用readResolve()方法來避免此事發生。但是,為了保證枚舉類型像Java規範中所說的那樣,每一個枚舉類型極其定義的枚舉變量在JVM中都是唯一的,在枚舉類型的序列化和反序列化上,Java做了特殊的規定。英文原文我就不貼了。

大概意思就是說,在序列化的時候Java僅僅是將枚舉對象的name屬性輸出到結果中,反序列化的時候則是通過java.lang.Enum的valueOf方法來根據名字查找枚舉對象。同時,編譯器是不允許任何對這種序列化機制的定製的,因此禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法。 我們看一下這個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類型是線程安全的。


- MORE | 更多精彩文章 -

如果你看到了這裡,說明你喜歡本文。

那麼請長按二維碼,關注Hollis

相關焦點

  • 【枚舉】用好 Java 中的枚舉,真的沒有那麼簡單!
    」1.概覽在本文中,我們將看到什麼是 Java 枚舉,它們解決了哪些問題以及如何在實踐中使用  Java 枚舉實現一些設計模式。enum關鍵字在 java5 中引入,表示一種特殊類型的類,其總是繼承java.lang.Enum類,更多內容可以自行查看其官方文檔。
  • 用好Java中的枚舉,真的沒有那麼簡單!
    」1.概覽在本文中,我們將看到什麼是 Java 枚舉,它們解決了哪些問題以及如何在實踐中使用  Java 枚舉實現一些設計模式。enum關鍵字在 java5 中引入,表示一種特殊類型的類,其總是繼承java.lang.Enum類,更多內容可以自行查看其官方文檔。
  • 高並發下線程安全的單例模式(最全最經典,值得收藏)
    在所有的設計模式中,單例模式是我們在項目開發中最為常見的設計模式之一,而單例模式有很多種實現方式,你是否都了解呢?高並發下如何保證單例模式的線程安全性呢?如何保證序列化後的單例對象在反序列化後任然是單例的呢?這些問題在看了本文之後都會一一的告訴你答案,趕快來閱讀吧!什麼是單例模式?
  • 用好 Java 中的枚舉,真的沒有那麼簡單!
    1.概覽在本文中,我們將看到什麼是 Java 枚舉,它們解決了哪些問題以及如何在實踐中使用  Java 枚舉實現一些設計模式。enum關鍵字在 java5 中引入,表示一種特殊類型的類,其總是繼承java.lang.Enum類,更多內容可以自行查看其官方文檔。
  • Java枚舉原來還能這麼用
    前言相信不少java開發者寫過狀態變更的業務,比如訂單流程、請假流程等等。一般會搞一個狀態標識來標識生命周期的某個階段。很多人會寫這種邏輯:如果流程幾十個豈不是要if到爆炸。還有這「0」、「1」是幾個意思?
  • 求職面試中的高頻 Java 問題
    這篇文章包含了超過  50 個 Java 面試問題,涵蓋了所有重要主題,例如 Java 基礎知識、Java 集合框架、Java 多線程與並發、Java IO、JDBC、JVM 內部原理、編碼問題和面向對象編程等等。
  • Java 枚舉類型使用,枚舉集合介紹
    枚舉類型從前面的數據類型章節可知 Java 中並沒有枚舉類型,但通過 Java API 中的 java.lang.Enum 枚舉類我們可以定義枚舉類型數據,本質上還是一個類類型。字符竄類型也是一樣,通過 String 類生成,每一個雙引號中的字符串編譯後都會創建對應的字符串 String 對象。為什麼要定義枚舉類型?
  • 阿里內部學習指南《Effective Java中文 第3版》程式設計師進階必備
    經典Jolt獲獎作品《Effective Java》的第3版這本書,對上一版內容進行了徹底的更新,介紹了如何充分利用從泛型到枚舉、從註解到自動裝箱的各種特性,幫助讀者更加有效地使用Java程式語言及其基本類庫:java.lang. java.util和java.io,以及子包,如java.util
  • 聽說這10道Java面試題90%的人都不會!!!
    如果 String 是可變的,加載「java.io.Writer」 的請求可能已被更改為加載 「mil.vogoon.DiskErasingWriter」. 安全性和字符串池是使字符串不可變的主要原因。順便說一句,上面的理由很好回答另一個Java面試問題: 「為什麼String在Java中是最終的」。要想是不可變的,你必須是最終的,這樣你的子類不會破壞不變性。你怎麼看?
  • Java程式設計師必備:序列化全方位解析
    java.io.ObjectInputStream表示對象輸入流,它的readObject()方法,從輸入流中讀取到字節序列,反序列化成為一個對象,最後將其返回。五、序列化的使用序列化如何使用? } }七、日常開發序列化的一些注意點static靜態變量和transient 修飾的欄位是不會被序列化的serialVersionUID問題如果某個序列化類的成員變量是對象類型,則該對象類型的類必須實現序列化子類實現了序列化,父類沒有實現序列化,父類中的欄位會丟失~static
  • 全方位解析Java序列化
    ,於是 Web 容器就會把一些 Session 先序列化到硬碟中,等要用了,再把保存在硬碟中的對象還原到內存中。, ClassNotFoundException;}java.io.ObjectOutputStream 類表示對象輸出流,它的 writeObject(Object obj) 方法可以對指定 obj 對象參數進行序列化,再把得到的字節序列寫到一個目標輸出流中。ja
  • Java Singleton設計模式DEMO
    動機和現實世界的例子在面向對象的設計中,某些類只有一個實例非常重要。那是因為它們代表了一種獨特的東西,這是同類產品中的一種。讓我們從Java語言中看到一些真實世界的Singletons示例,以了解這意味著什麼 -java.lang.Runtime:Java提供了一個Runtime類,它表示運行應用程式的當前運行時環境。
  • 支付寶面試:什麼是序列化,怎麼序列化,為什麼序列化,反序列化會遇到什麼問題,如何解決?
    二、什麼情況下需要序列化當你想把的內存中的對象狀態保存到一個文件中或者資料庫中時候;(老實說,上面的幾種,我可能就用過個存資料庫的),以及會產生的bug問題。,把得到的字節序列寫到一個目標輸出流中。(你還可以反過來,帶ID去序列化,然後,沒ID去反序列化。也是同樣的問題。)
  • 【Java拾遺】不可不知的 Java 序列化
    序列化是什麼百度百科中給序列化的定義是『序列化 (Serialization)是將對象的狀態信息轉換為可以存儲或傳輸的形式的過程。』。似乎有點抽象,下面用一個例子簡單類比一下。日常生活中,總少不了人跟人之間的交流與溝通。而溝通的前提是先要把我們大腦中想的內容,通過某種形式表達出來。然後別人再通過我們表達出的內容去理解。
  • 一文搞懂 Java 中的枚舉,寫得非常好!
    此外,java.lang.Enum實現了Comparable和 Serializable接口,所以也提供 compareTo()方法。關於Java序列化你應該知道的一切,推薦大家閱讀。枚舉可以添加方法在概念章節提到了,枚舉值默認為從0開始的有序數值。那麼問題來了:如何為枚舉顯示的賦值。Java 不允許使用 = 為枚舉常量賦值如果你接觸過C/C++,你肯定會很自然的想到賦值符號 =。
  • Java枚舉深度解讀,看這篇就夠了
    沒錯,這是 jdk 5.0之前的痛點,為了解決實例數量固定,便於維護這些問題,在jdk 5.0之後更新Enum枚舉類解決了這個問題。那在jdk 5.0之前官方是怎麼做的呢?難道需要我們一個個去記住 Calendar 的數字?
  • 為啥用枚舉,枚舉有哪些用法?
    Java基礎:枚舉的用法與原理在學習過程中,我們也只是在定義常量的時候,會意識到枚舉的存在,而定義常量其實可以在類中實現,這時就會感覺枚舉有點雞肋。但在實際項目開發的過程中,枚舉因相當迷人的特性而受到越來越多的關注。本文將按以下小節點來,一一介紹枚舉:1.
  • JAVA反序列化—FastJson抗爭的一生
    就先一路補了很多其他知識點,RMI反序列化,JNDI注入,7u21鏈等(就是之前的文章),之後也是拖了很長時間,花了很長時間,總算把這篇一開始就想寫的文,給補完了。類似的文是已經有了不少,學習也是基於前輩們的文章一步步走來,但是個人習慣於把所有問題理清楚,講清楚。理應是把大佬們的文要細緻些。
  • java序列化反序列化中serialVersionUID到底有什麼用
    前言:在回答上面的問題之前,首先要知道什麼是序列化、反序列化、用途是什麼、實現的必要條件。序列化\反序列化:java序列化是指把java對象轉換為字節序列的過程,而java反序列化是指把字節序列恢復為java對象的過程。
  • 什麼是序列化,怎麼序列化,為什麼序列化,反序列化會遇到什麼問題,如何解決.
    我剛剛見到這個關鍵字 Serializable 的時候,就有如上的這麼些問題。在處理這個問題之前,你要先知道一個問題,這個比較重要。這個Serializable接口,以及相關的東西,全部都在 Java io 裡面的。