枚舉是怎麼保證線程安全的呢?
枚舉類型是什麼類呢?是 enum 嗎?答案明顯不是,enum 和 class 同樣,只是一個關鍵字,他並非一個類,那枚舉是由什麼類展現的呢,下面是一個枚舉:java
1public enum Test {
2 A, B, C, D;
3}
上面代碼反編譯結果以下:web
1public final class Test extends Enum {
2 private Test(String s, int i) {
3 super(s, i);
4 }
5 public static Test[] values() {
6 Test at[];
7 int i;
8 Test at1[];
9 System.arraycopy(at = ENUM$VALUES, 0, at1 = new Test[i = at.length], 0, i);
10 return at1;
11 }
12
13 public static Test valueOf(String s) {
14 return (Test)Enum.valueOf(test/Test, s);
15 }
16
17 ......
18}
能夠經過反編譯看到,public final class Test extends Enum,說明該枚舉類繼承了 Enum 類,同時 final 關鍵字告訴咱們這個類不能被繼承。當咱們使用 enum 定義一個枚舉類型時,編譯器會自動幫咱們建立一個 final 類型的類繼承 Enum 類,因此枚舉類型不能被繼承。安全
其反編譯結果中能看到以下屬性的定義和初始化:微信
1public final class Test extends Enum {
2 ......
3 public static final Test A;
4 public static final Test B;
5 public static final Test C;
6 public static final Test D;
7 private static final Test ENUM$VALUES[];
8 static {
9 A = new T("A", 0);
10 B = new T("B", 1);
11 C = new T("C", 2);
12 D = new T("D", 3);
13 ENUM$VALUES = (new T[] {
14 A, B, C, D
15 });
16 }
17}
枚舉成員屬性都是 static 類型,而 static 類型的屬性會在類被加載以後被初始化,而當一個 Java 類第一次被真正使用到的時候靜態資源被初始化、Java 類的加載和初始化過程都是線程安全的,因此能夠發現建立一個 enum 類型是線程安全的。app
枚舉實現的單例爲啥是最好的方式?
在單例模式中,咱們看到業界通常一共有六種實現單例的方式,其中 Effective Java 的做者提倡你們使用枚舉的方式,爲何這麼提倡呢?編輯器
寫法簡單這個你們看看本身對比下單例模式的各類實現方式就知道區別了。flex
1public enum Singleton{
2 INSTANCE;
3}
咱們能夠經過 Singleton.INSTANCE 來訪問這個單例。
url
咱們知道,其餘的各類單例模式寫法都有一個比較大的問題,就是一旦實現了 Serializable 接口以後,就再也不是單例得了,由於序列化反序列化時每次調用 readObject() 方法返回的都是一個新建立出來的對象,有一種解決辦法就是使用 readResolve() 方法來避免此事發生。spa
可是爲了保證枚舉類型像 Java 規範中所說的那樣,每個枚舉類型極其定義的枚舉變量在 JVM 中都是惟一的,在枚舉類型的序列化和反序列化上,Java 作了特殊的規定。在序列化的時候 Java 僅僅是將枚舉對象的 name 屬性輸出到結果中,反序列化的時候則是經過 java.lang.Enum 的 valueOf 方法來根據名字查找枚舉對象。同時編譯器不容許任何對這種序列化機制進行定製修改,因此禁用了 writeObject、readObject、readObjectNoData、writeReplace 和 readResolve 等方法。咱們看一下這個 valueOf 方法:.net
1public 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 對序列化有保證。
瞭解 Java ClassLoader 和類加載初始化機制的小夥伴都明白(不懂的點擊左下角閱讀原文看歷史文章吧),當一個 Java 類第一次被真正使用到的時候靜態資源被初始化、Java 類的加載和初始化過程都是線程安全的,因此建立一個 enum 類型是線程安全的。

點擊左下角閱讀原文查看歷史經典技術問題彙總,看完順手走一波PYQ呀~
本文分享自微信公衆號 - 碼農每日一題(DailyCoder)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。