Java中列舉的執行緒安全性及序列化問題

java填坑路發表於2018-08-05

–列舉是如何保證執行緒安全的

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

 

然後我們使用反編譯,看看這段程式碼到底是怎麼實現的,反編譯(Java的反編譯)後程式碼內容如下:

 

通過反編譯後程式碼我們可以看到,public final class T extends Enum,說明,該類是繼承了Enum類的,同時final關鍵字告訴我們,這個類也是不能被繼承的。當我們使用enmu來定義一個列舉型別的時候,編譯器會自動幫我們建立一個final型別的類繼承Enum類,所以列舉型別不能被繼承,我們看到這個類中有幾個屬性和方法。

我們可以看到:

都是static型別的,因為static型別的屬性會在類被載入之後被初始化,我們在深度分析Java的ClassLoader機制(原始碼級別)和Java類的載入、連結和初始化兩個文章中分別介紹過,當一個Java類第一次被真正使用到的時候靜態資源被初始化、Java類的載入和初始化過程都是執行緒安全的。所以,建立一個enum型別是執行緒安全的

–為什麼用列舉實現的單例是最好的方式

單例模式的七種寫法中,我們看到一共有七種實現單例的方式,其中,Effective Java作者Josh Bloch 提倡使用列舉的方式,既然大神說這種方式好,那我們就要知道它為什麼好?

關於這個問題,我有一篇為什麼我牆裂建議大家使用列舉來實現單例。單獨介紹過,這裡再回顧一下。

1. 列舉寫法簡單

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

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

2. 列舉自己處理序列化

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

大概意思就是說,在序列化的時候Java僅僅是將列舉物件的name屬性輸出到結果中,反序列化的時候則是通過java.lang.Enum的valueOf方法來根據名字查詢列舉物件。同時,編譯器是不允許任何對這種序列化機制的定製的,因此禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法。 我們看一下這個valueOf方法:

從程式碼中可以看到,程式碼會嘗試從呼叫enumType這個Class物件的enumConstantDirectory()方法返回的map中獲取名字為name的列舉物件,如果不存在就會丟擲異常。再進一步跟到enumConstantDirectory()方法,就會發現到最後會以反射的方式呼叫enumType這個型別的values()靜態方法,也就是上面我們看到的編譯器為我們建立的那個方法,然後用返回結果填充enumType這個Class物件中的enumConstantDirectory屬性。

所以,JVM對序列化有保證。

3.列舉例項建立是thread-safe(執行緒安全的)

我們在深度分析Java的ClassLoader機制(原始碼級別)和Java類的載入、連結和初始化兩個文章中分別介紹過,當一個Java類第一次被真正使用到的時候靜態資源被初始化、Java類的載入和初始化過程都是執行緒安全的。所以,建立一個enum型別是執行緒安全的。

歡迎工作一到五年的Java工程師朋友們加入Java架構開發:744677563

本群提供免費的學習指導 架構資料 以及免費的解答

不懂得問題都可以在本群提出來 之後還會有職業生涯規劃以及面試指導


相關文章