單例模式簡介
單例模式是 Java 中最簡單珠漂,也是最基礎(chǔ)晚缩,最常用的設(shè)計(jì)模式之一。在運(yùn)行期間媳危,保證某個類只創(chuàng)建一個實(shí)例荞彼,保證一個類僅有一個實(shí)例,并提供一個訪問它的全局訪問點(diǎn)待笑。下面就來講講Java中的N種實(shí)現(xiàn)單例模式的寫法鸣皂。
原創(chuàng)聲明
本文發(fā)布于簡書【Happyjava】。Happy的簡書地址:http://www.reibang.com/u/f69002e01708暮蹂,Happy的個人博客:http://blog.happyjava.cn寞缝。歡迎轉(zhuǎn)載,但須保留此段聲明仰泻。
餓漢式
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
這是實(shí)現(xiàn)一個安全的單例模式的最簡單粗暴的寫法荆陆,這種實(shí)現(xiàn)方式我們稱之為餓漢式。之所以稱之為餓漢式我纪,是因?yàn)槎亲雍莛I了慎宾,想馬上吃到東西丐吓,不想等待生產(chǎn)時間浅悉。這種寫法,在類被加載的時候就把Singleton實(shí)例給創(chuàng)建出來了券犁。
餓漢式的缺點(diǎn)就是术健,可能在還不需要此實(shí)例的時候就已經(jīng)把實(shí)例創(chuàng)建出來了,沒起到lazy loading的效果粘衬。優(yōu)點(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;
}
}
相比餓漢式稚新,懶漢式顯得沒那么“餓”勘伺,在真正需要的時候再去創(chuàng)建實(shí)例。在getInstance方法中褂删,先判斷實(shí)例是否為空再決定是否去創(chuàng)建實(shí)例飞醉,看起來似乎很完美,但是存在線程安全問題屯阀。在并發(fā)獲取實(shí)例的時候缅帘,可能會存在構(gòu)建了多個實(shí)例的情況。所以难衰,需要對此代碼進(jìn)行下改進(jìn)钦无。
public class SingletonSafe {
private static volatile SingletonSafe singleton;
private SingletonSafe() {
}
public static SingletonSafe getSingleton() {
if (singleton == null) {
synchronized (SingletonSafe.class) {
if (singleton == null) {
singleton = new SingletonSafe();
}
}
}
return singleton;
}
}
這里采用了雙重校驗(yàn)的方式,對懶漢式單例模式做了線程安全處理盖袭。通過加鎖失暂,可以保證同時只有一個線程走到第二個判空代碼中去彼宠,這樣保證了只創(chuàng)建 一個實(shí)例。這里還用到了volatile關(guān)鍵字來修飾singleton趣席,其最關(guān)鍵的作用是防止指令重排兵志。
靜態(tài)內(nèi)部類
public class Singleton {
private static class SingletonHolder {
private static Singleton instance = new Singleton();
}
private Singleton() {
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
}
通過靜態(tài)內(nèi)部類的方式實(shí)現(xiàn)單例模式是線程安全的,同時靜態(tài)內(nèi)部類不會在Singleton類加載時就加載宣肚,而是在調(diào)用getInstance()方法時才進(jìn)行加載想罕,達(dá)到了懶加載的效果。
似乎靜態(tài)內(nèi)部類看起來已經(jīng)是最完美的方法了霉涨,其實(shí)不是按价,可能還存在反射攻擊或者反序列化攻擊。且看如下代碼:
public static void main(String[] args) throws Exception {
Singleton singleton = Singleton.getInstance();
Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton newSingleton = constructor.newInstance();
System.out.println(singleton == newSingleton);
}
運(yùn)行結(jié)果:
通過結(jié)果看笙瑟,這兩個實(shí)例不是同一個楼镐,這就違背了單例模式的原則了。
除了反射攻擊之外往枷,還可能存在反序列化攻擊的情況框产。如下:
引入依賴:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.8.1</version>
</dependency>
這個依賴提供了序列化和反序列化工具類。
Singleton類實(shí)現(xiàn)java.io.Serializable接口错洁。
如下:
public class Singleton implements Serializable {
private static class SingletonHolder {
private static Singleton instance = new Singleton();
}
private Singleton() {
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
public static void main(String[] args) {
Singleton instance = Singleton.getInstance();
byte[] serialize = SerializationUtils.serialize(instance);
Singleton newInstance = SerializationUtils.deserialize(serialize);
System.out.println(instance == newInstance);
}
}
運(yùn)行結(jié)果:
通過枚舉實(shí)現(xiàn)單例模式
在effective java(這本書真的很棒)中說道秉宿,最佳的單例實(shí)現(xiàn)模式就是枚舉模式。利用枚舉的特性屯碴,讓JVM來幫我們保證線程安全和單一實(shí)例的問題描睦。除此之外,寫法還特別簡單导而。
public enum Singleton {
INSTANCE;
public void doSomething() {
System.out.println("doSomething");
}
}
調(diào)用方法:
public class Main {
public static void main(String[] args) {
Singleton.INSTANCE.doSomething();
}
}
直接通過Singleton.INSTANCE.doSomething()的方式調(diào)用即可忱叭。方便、簡潔又安全今艺。
總結(jié)
以上列舉了多種單例模式的寫法韵丑,分析了其利弊之處。同時還介紹了目前最佳的單例寫法——枚舉模式虚缎,相信在未來撵彻,枚舉模式的單例寫法也會越來越流行。