1. 引言
單例模式(Singleton Pattern)是 Java 中最簡單的設(shè)計(jì)模式之一慨蛙,屬于設(shè)計(jì)模式中的創(chuàng)建型模式赁咙,這種模式涉及到一個單一的類拢军,該類負(fù)責(zé)創(chuàng)建自己的對象尉剩,同時確保只有單個對象被創(chuàng)建迹冤。單例模式應(yīng)用于要求只存在一個單例類對象的場景菌瘫,如線程池奈偏,注冊表飞几,如果創(chuàng)建出不只一個該類的實(shí)例那就會出現(xiàn)問題后室。
2. 注意
單例模式的實(shí)現(xiàn)有多種不同的實(shí)現(xiàn)缩膝,每種實(shí)現(xiàn)都有其優(yōu)缺點(diǎn),有些缺點(diǎn)明顯岸霹,選擇合適的實(shí)現(xiàn)非常重要疾层。
3. 實(shí)現(xiàn)
餓漢式
優(yōu)點(diǎn) | 缺點(diǎn) |
---|---|
線程安全 | 非懶加載 |
效率高 | 不能阻止反射攻擊 |
實(shí)現(xiàn)簡單 |
class Singleton{
private static final Singleton INSTANCE = new Singleton();
private Singleton(){};
public static Singleton getInstance(){
return INSTANCE;
}
}
優(yōu)點(diǎn):
- 同樣餓漢式里的線程安全性是由類加載機(jī)制保證。
- 因?yàn)檎{(diào)用方法獲取實(shí)例時不需要進(jìn)行同步贡避,相比需要線程同步的實(shí)現(xiàn)方式效率更高痛黎。
- 代碼量少予弧,實(shí)現(xiàn)簡單
缺點(diǎn):
- 非懶加載是指類初始化時就創(chuàng)建實(shí)例對象,而不等到需要該對象時調(diào)用
getInstance()
方法才創(chuàng)建對象湖饱,導(dǎo)致提早創(chuàng)建對象桌肴,如果對象很大時而之后不需要用,那就太占有內(nèi)存了琉历。- 即使構(gòu)造函數(shù)是私有的坠七,但是卻可以利用反射機(jī)制去訪問構(gòu)造函數(shù)從而創(chuàng)建出不只一個對象,這就違背了單例模式的原則旗笔。
懶漢式(非線程安全)
優(yōu)點(diǎn) | 缺點(diǎn) |
---|---|
懶加載 | 非線程安全 |
效率高 | 不能阻止反射攻擊 |
實(shí)現(xiàn)簡單 |
public class Singleton {
private static Singleton instance;
private Singleton(){};
public static Singleton getInstance(){
if (instance == null)
instance = new Singleton();
return instance;
}
}
缺點(diǎn): 不具備線程安全性彪置,嚴(yán)格意義來說不能算單例模式。
懶漢式(線程安全)
優(yōu)點(diǎn) | 缺點(diǎn) |
---|---|
線程安全 | 效率低 |
懶加載 | 不能阻止反射攻擊 |
實(shí)現(xiàn)簡單 |
public class Singleton {
private static Singleton instance;
private Singleton(){};
public static synchronized Singleton getInstance(){
if (instance == null)
instance = new Singleton();
return instance;
}
}
缺點(diǎn):雖然實(shí)現(xiàn)了線程安全性蝇恶,但是這種實(shí)現(xiàn)方法導(dǎo)致每次訪問
getInstance()
方法時都要加鎖拳魁,效率大大下降。
雙重檢驗(yàn)鎖(Double Checked Locking)
優(yōu)點(diǎn) | 缺點(diǎn) |
---|---|
線程安全 | 不能阻止反射攻擊 |
效率高 | 較復(fù)雜 |
懶加載 |
class Singleton{
private volatile static Singleton instance;
private Singleton(){};
public static Singleton getInstance(){
if (instance == null){
synchronized (Singleton.class){
if (instance == null)
instance = new Singleton();
}
}
return instance;
}
}
優(yōu)點(diǎn):DCL的線程安全性是通過加鎖來保證的撮弧,而且加鎖的過程只是發(fā)生在第一次獲取對象的時候潘懊,所以效率還是很高。還有一個重要的點(diǎn)就是
instance
字段要用volatile
修飾來禁止指令重排贿衍。
靜態(tài)內(nèi)部類(Static Inner Class)
優(yōu)點(diǎn) | 缺點(diǎn) |
---|---|
線程安全 | 不能阻止反射攻擊 |
效率高 | |
懶加載 |
class Singleton{
private Singleton(){};
private static class SingletonHolder{
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
}
枚舉(Enum)
優(yōu)點(diǎn) | 缺點(diǎn) |
---|---|
線程安全 | 非懶加載 |
效率高 | |
實(shí)現(xiàn)簡單 | |
可阻止反射攻擊 | |
提供序列化機(jī)制授舟,并且反序列化時可避免出現(xiàn)多個實(shí)例 |
enum Singleton {
INSTANCE;
}
優(yōu)點(diǎn):
- 枚舉里面的
INSTANCE
字段是public static final Singleton
類型的,線程安全性有類加載階段保證贸辈,因此不需要進(jìn)行額外的同步操作释树,效率高,實(shí)現(xiàn)簡單擎淤。- 反射機(jī)制已經(jīng)針對了枚舉類型的對象做了處理奢啥,可以保證不能通過反射機(jī)制創(chuàng)建多個對象,所以可以阻止反射攻擊嘴拢。
- 同樣的桩盲,序列化機(jī)制也對枚舉類型的對象做了處理,在序列化的時候僅僅是將枚舉對象的name屬性序列化席吴,反序列化的時候則是通過Enum 的 valueOf() 方法來根據(jù)名字查找枚舉對象赌结。因此反序列化后的實(shí)例也會和之前被序列化的對象實(shí)例相同。
現(xiàn)在發(fā)現(xiàn)只有枚舉提供了阻止反射攻擊和避免反序列化多個不同實(shí)例的序列化機(jī)制抢腐,那其他實(shí)現(xiàn)方法可不可以提供這兩種功能呢姑曙,答案是可以的襟交。
1. 對于反射來說迈倍,雖然構(gòu)造函數(shù)是私有的,但反射可以通過訪問私有的構(gòu)造函數(shù)來創(chuàng)建多個對象捣域。既然是單例模式啼染,那么我們可以在創(chuàng)建第二個拋出異常宴合。只需修改構(gòu)造函數(shù),如下:
private static boolean TAG;
private Singleton(){
if (TAG){
throw new IllegalArgumentException("Cannot create object");
}else {
TAG = true;
}
}
2.要讓對象支持序列化操作迹鹅,該對象的類需要implements Serializable
卦洽,并且在類中增加一個方法private Object readResolve()
,這個方法會在反序列化完后調(diào)用斜棚,其中返回的對象替代了原來反序列化生成的對象阀蒂。如下:
private Object readResolve(){
return INSTANCE;
}
4. 最后
就以上來看,我覺得實(shí)現(xiàn)單例的最佳方式應(yīng)該是靜態(tài)內(nèi)部類和枚舉弟蚀,其中靜態(tài)內(nèi)部類真是優(yōu)點(diǎn)滿滿啊蚤霞。對于枚舉來說,別看簡簡單單只寫出兩三行代碼义钉,其實(shí)它是java的一顆語法糖昧绣,反編譯過后你就知道它干貨滿滿。而且要知道它背后反射和序列化對它做的優(yōu)化捶闸,知識點(diǎn)真心不少夜畴。