日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

为什么我墙裂建议大家使用枚举来实现单例。

發(fā)布時(shí)間:2025/3/16 编程问答 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 为什么我墙裂建议大家使用枚举来实现单例。 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

關(guān)于單例模式,我的博客中有很多文章介紹過。作為23種設(shè)計(jì)模式中最為常用的設(shè)計(jì)模式,單例模式并沒有想象的那么簡單。因?yàn)樵谠O(shè)計(jì)單例的時(shí)候要考慮很多問題,比如線程安全問題、序列化對單例的破壞等。

單例相關(guān)文章一覽:

設(shè)計(jì)模式(二)——單例模式

設(shè)計(jì)模式(三)——JDK中的那些單例

單例模式的七種寫法

單例與序列化的那些事兒

不使用synchronized和lock,如何實(shí)現(xiàn)一個(gè)線程安全的單例?

不使用synchronized和lock,如何實(shí)現(xiàn)一個(gè)線程安全的單例?(二)

如果你對單例不是很了解,或者對于單例的線程安全問題以及序列化會(huì)破壞單例等問題不是很清楚,可以先閱讀以上文章。上面六篇文章看完之后,相信你一定會(huì)對單例模式有更多,更深入的理解。

我們知道,單例模式,一般有七種寫法,那么這七種寫法中,最好的是哪一種呢?為什么呢?本文就來抽絲剝繭一下。

哪種寫單例的方式最好

在StakcOverflow中,有一個(gè)關(guān)于What is an efficient way to implement a singleton pattern in Java?的討論:

如上圖,得票率最高的回答是:使用枚舉。

回答者引用了Joshua Bloch大神在《Effective Java》中明確表達(dá)過的觀點(diǎn):

使用枚舉實(shí)現(xiàn)單例的方法雖然還沒有廣泛采用,但是單元素的枚舉類型已經(jīng)成為實(shí)現(xiàn)Singleton的最佳方法。

如果你真的深入理解了單例的用法以及一些可能存在的坑的話,那么你也許也能得到相同的結(jié)論,那就是:使用枚舉實(shí)現(xiàn)單例是一種很好的方法。

枚舉單例寫法簡單

如果你看過《單例模式的七種寫法》中的實(shí)現(xiàn)單例的所有方式的代碼,那就會(huì)發(fā)現(xiàn),各種方式實(shí)現(xiàn)單例的代碼都比較復(fù)雜。主要原因是在考慮線程安全問題。

我們簡單對比下“雙重校驗(yàn)鎖”方式和枚舉方式實(shí)現(xiàn)單例的代碼。

“雙重校驗(yàn)鎖”實(shí)現(xiàn)單例:

public class Singleton { private volatile static Singleton singleton; private Singleton (){} public static Singleton getSingleton() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } }

枚舉實(shí)現(xiàn)單例:

public enum Singleton { INSTANCE; public void whateverMethod() { } }

相比之下,你就會(huì)發(fā)現(xiàn),枚舉實(shí)現(xiàn)單例的代碼會(huì)精簡很多。

上面的雙重鎖校驗(yàn)的代碼之所以很臃腫,是因?yàn)榇蟛糠执a都是在保證線程安全。為了在保證線程安全和鎖粒度之間做權(quán)衡,代碼難免會(huì)寫的復(fù)雜些。但是,這段代碼還是有問題的,因?yàn)樗麩o法解決反序列化會(huì)破壞單例的問題。

枚舉可解決線程安全問題

上面提到過。使用非枚舉的方式實(shí)現(xiàn)單例,都要自己來保證線程安全,所以,這就導(dǎo)致其他方法必然是比較臃腫的。那么,為什么使用枚舉就不需要解決線程安全問題呢?

其實(shí),并不是使用枚舉就不需要保證線程安全,只不過線程安全的保證不需要我們關(guān)心而已。也就是說,其實(shí)在“底層”還是做了線程安全方面的保證的。

那么,“底層”到底指的是什么?

這就要說到關(guān)于枚舉的實(shí)現(xiàn)了。這部分內(nèi)容可以參考我的另外一篇博文深度分析Java的枚舉類型—-枚舉的線程安全性及序列化問題,這里我簡單說明一下:

定義枚舉時(shí)使用enum和class一樣,是Java中的一個(gè)關(guān)鍵字。就像class對應(yīng)用一個(gè)Class類一樣,enum也對應(yīng)有一個(gè)Enum類。

通過將定義好的枚舉反編譯,我們就能發(fā)現(xiàn),其實(shí)枚舉在經(jīng)過javac的編譯之后,會(huì)被轉(zhuǎn)換成形如public final class T extends Enum的定義。

而且,枚舉中的各個(gè)枚舉項(xiàng)同事通過static來定義的。如:

public enum T {SPRING,SUMMER,AUTUMN,WINTER; }

反編譯后代碼為:

public final class T extends Enum {//省略部分內(nèi)容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});} }

了解JVM的類加載機(jī)制的朋友應(yīng)該對這部分比較清楚。static類型的屬性會(huì)在類被加載之后被初始化,我們在深度分析Java的ClassLoader機(jī)制(源碼級別)和Java類的加載、鏈接和初始化兩個(gè)文章中分別介紹過,當(dāng)一個(gè)Java類第一次被真正使用到的時(shí)候靜態(tài)資源被初始化、Java類的加載和初始化過程都是線程安全的(因?yàn)樘摂M機(jī)在加載枚舉的類的時(shí)候,會(huì)使用ClassLoader的loadClass方法,而這個(gè)方法使用同步代碼塊保證了線程安全)。所以,創(chuàng)建一個(gè)enum類型是線程安全的。

也就是說,我們定義的一個(gè)枚舉,在第一次被真正用到的時(shí)候,會(huì)被虛擬機(jī)加載并初始化,而這個(gè)初始化過程是線程安全的。而我們知道,解決單例的并發(fā)問題,主要解決的就是初始化過程中的線程安全問題。

所以,由于枚舉的以上特性,枚舉實(shí)現(xiàn)的單例是天生線程安全的。

枚舉可解決反序列化會(huì)破壞單例的問題

前面我們提到過,就是使用雙重校驗(yàn)鎖實(shí)現(xiàn)的單例其實(shí)是存在一定問題的,就是這種單例有可能被序列化鎖破壞,關(guān)于這種破壞及解決辦法,參看單例與序列化的那些事兒,這里不做更加詳細(xì)的說明了。

那么,對于序列化這件事情,為什么枚舉又有先天的優(yōu)勢了呢?答案可以在Java Object Serialization Specification 中找到答案。其中專門對枚舉的序列化做了如下規(guī)定:

大概意思就是:在序列化的時(shí)候Java僅僅是將枚舉對象的name屬性輸出到結(jié)果中,反序列化的時(shí)候則是通過java.lang.Enum的valueOf方法來根據(jù)名字查找枚舉對象。同時(shí),編譯器是不允許任何對這種序列化機(jī)制的定制的,因此禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法。

普通的Java類的反序列化過程中,會(huì)通過反射調(diào)用類的默認(rèn)構(gòu)造函數(shù)來初始化對象。所以,即使單例中構(gòu)造函數(shù)是私有的,也會(huì)被反射給破壞掉。由于反序列化后的對象是重新new出來的,所以這就破壞了單例。

但是,枚舉的反序列化并不是通過反射實(shí)現(xiàn)的。所以,也就不會(huì)發(fā)生由于反序列化導(dǎo)致的單例破壞問題。這部分內(nèi)容在深度分析Java的枚舉類型—-枚舉的線程安全性及序列化問題中也有更加詳細(xì)的介紹,還展示了部分代碼,感興趣的朋友可以前往閱讀。

總結(jié)

在所有的單例實(shí)現(xiàn)方式中,枚舉是一種在代碼寫法上最簡單的方式,之所以代碼十分簡潔,是因?yàn)镴ava給我們提供了enum關(guān)鍵字,我們便可以很方便的聲明一個(gè)枚舉類型,而不需要關(guān)心其初始化過程中的線程安全問題,因?yàn)槊杜e類在被虛擬機(jī)加載的時(shí)候會(huì)保證線程安全的被初始化。

除此之外,在序列化方面,Java中有明確規(guī)定,枚舉的序列化和反序列化是有特殊定制的。這就可以避免反序列化過程中由于反射而導(dǎo)致的單例被破壞問題。

總結(jié)

以上是生活随笔為你收集整理的为什么我墙裂建议大家使用枚举来实现单例。的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。