Java中枚举的线程安全性及序列化问题
轉(zhuǎn)載自??Java中枚舉的線程安全性及序列化問題
Java SE5提供了一種新的類型-Java的枚舉類型,關(guān)鍵字enum可以將一組具名的值的有限集合創(chuàng)建為一種新的類型,而這些具名的值可以作為常規(guī)的程序組件使用,這是一種非常有用的功能。本文將深入分析枚舉的源碼,看一看枚舉是怎么實(shí)現(xiàn)的,他是如何保證線程安全的,以及為什么用枚舉實(shí)現(xiàn)的單例是最好的方式。
?
枚舉是如何保證線程安全的
要想看源碼,首先得有一個(gè)類吧,那么枚舉類型到底是什么類呢?是enum嗎?答案很明顯不是,enum就和class一樣,只是一個(gè)關(guān)鍵字,他并不是一個(gè)類,那么枚舉是由什么類維護(hù)的呢,我們簡單的寫一個(gè)枚舉:
public?enum?t?{SPRING,SUMMER,AUTUMN,WINTER; }然后我們使用反編譯,看看這段代碼到底是怎么實(shí)現(xiàn)的,反編譯(Java的反編譯)后代碼內(nèi)容如下:
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類的,同時(shí)final關(guān)鍵字告訴我們,這個(gè)類也是不能被繼承的。當(dāng)我們使用enmu來定義一個(gè)枚舉類型的時(shí)候,編譯器會(huì)自動(dòng)幫我們創(chuàng)建一個(gè)final類型的類繼承Enum類,所以枚舉類型不能被繼承,我們看到這個(gè)類中有幾個(gè)屬性和方法。
我們可以看到:
????????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類型的,因?yàn)閟tatic類型的屬性會(huì)在類被加載之后被初始化,我們在深度分析Java的ClassLoader機(jī)制(源碼級別)和Java類的加載、鏈接和初始化兩個(gè)文章中分別介紹過,當(dāng)一個(gè)Java類第一次被真正使用到的時(shí)候靜態(tài)資源被初始化、Java類的加載和初始化過程都是線程安全的。所以,創(chuàng)建一個(gè)enum類型是線程安全的。
?
為什么用枚舉實(shí)現(xiàn)的單例是最好的方式
在單例模式的七種寫法中,我們看到一共有七種實(shí)現(xiàn)單例的方式,其中,Effective Java作者Josh Bloch?提倡使用枚舉的方式,既然大神說這種方式好,那我們就要知道它為什么好?
關(guān)于這個(gè)問題,我有一篇為什么我墻裂建議大家使用枚舉來實(shí)現(xiàn)單例。單獨(dú)介紹過,這里再回顧一下。
1. 枚舉寫法簡單
寫法簡單這個(gè)大家看看單例模式的七種寫法里面的實(shí)現(xiàn)就知道區(qū)別了。
public?enum?EasySingleton{INSTANCE; }你可以通過EasySingleton.INSTANCE來訪問。
2. 枚舉自己處理序列化
我們知道,以前的所有的單例模式都有一個(gè)比較大的問題,就是一旦實(shí)現(xiàn)了Serializable接口之后,就不再是單例得了,因?yàn)?#xff0c;每次調(diào)用 readObject()方法返回的都是一個(gè)新創(chuàng)建出來的對象,有一種解決辦法就是使用readResolve()方法來避免此事發(fā)生。但是,為了保證枚舉類型像Java規(guī)范中所說的那樣,每一個(gè)枚舉類型極其定義的枚舉變量在JVM中都是唯一的,在枚舉類型的序列化和反序列化上,Java做了特殊的規(guī)定。英文原文我就不貼了。
大概意思就是說,在序列化的時(shí)候Java僅僅是將枚舉對象的name屬性輸出到結(jié)果中,反序列化的時(shí)候則是通過java.lang.Enum的valueOf方法來根據(jù)名字查找枚舉對象。同時(shí),編譯器是不允許任何對這種序列化機(jī)制的定制的,因此禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法。 我們看一下這個(gè)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);??}??從代碼中可以看到,代碼會(huì)嘗試從調(diào)用enumType這個(gè)Class對象的enumConstantDirectory()方法返回的map中獲取名字為name的枚舉對象,如果不存在就會(huì)拋出異常。再進(jìn)一步跟到enumConstantDirectory()方法,就會(huì)發(fā)現(xiàn)到最后會(huì)以反射的方式調(diào)用enumType這個(gè)類型的values()靜態(tài)方法,也就是上面我們看到的編譯器為我們創(chuàng)建的那個(gè)方法,然后用返回結(jié)果填充enumType這個(gè)Class對象中的enumConstantDirectory屬性。
所以,JVM對序列化有保證。
3.枚舉實(shí)例創(chuàng)建是thread-safe(線程安全的)
我們在深度分析Java的ClassLoader機(jī)制(源碼級別)和Java類的加載、鏈接和初始化兩個(gè)文章中分別介紹過,當(dāng)一個(gè)Java類第一次被真正使用到的時(shí)候靜態(tài)資源被初始化、Java類的加載和初始化過程都是線程安全的。所以,創(chuàng)建一個(gè)enum類型是線程安全的。
總結(jié)
以上是生活随笔為你收集整理的Java中枚举的线程安全性及序列化问题的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Windows远程桌面不安全Window
- 下一篇: win10系统默认网关怎么设置如何设置网