單例模式是一種簡(jiǎn)單且最常用的設(shè)計(jì)模式,有人可能會(huì)說(shuō)我也沒(méi)有用過(guò)單例模式,那如果你是一枚可愛(ài)的Java程序猿/媛,那你肯定用過(guò)Spring吧,而Spring Bean默認(rèn)的構(gòu)建模式都是單例的,而Spring為什么會(huì)這么做呢,顯然這和單例的優(yōu)勢(shì)有關(guān)。
什么是單例模式?
屬于創(chuàng)建類型的一種常用的軟件設(shè)計(jì)模式。通過(guò)單例模式的方法創(chuàng)建的類在當(dāng)前進(jìn)程中只有一個(gè)實(shí)例(根據(jù)需要,也有可能一個(gè)線程中屬于單例,如:僅線程上下文內(nèi)使用同一個(gè)實(shí)例)(引用自百度百科)
為什么使用單例模式
在應(yīng)用或者服務(wù)運(yùn)行過(guò)程中,有些類的實(shí)例是沒(méi)有必要每次都新建的,而減少新建實(shí)例帶來(lái)的性能損耗也成為了使用單例帶來(lái)的巨大好處;比如Java在新建一個(gè)實(shí)例時(shí)則需要分配內(nèi)存,給成員變量賦值等操作,當(dāng)這種操作很頻繁,不但加大了系統(tǒng)的開(kāi)銷,也增加了觸發(fā)GC的頻率。這就如同你新找了一個(gè)對(duì)象,又要從請(qǐng)吃飯看電影,拉手,親親……開(kāi)始,傷錢傷身體。因此諸如Spring也會(huì)大量使用單例,來(lái)節(jié)省資源減少系統(tǒng)開(kāi)銷。
如何實(shí)現(xiàn)單例
實(shí)現(xiàn)單例模式通過(guò)分為懶漢和餓漢兩種方式;其中懶漢(lazy)即是在使用的時(shí)候才創(chuàng)建實(shí)例,不使用則永遠(yuǎn)不會(huì)創(chuàng)建實(shí)例,餓漢則是應(yīng)用啟動(dòng)時(shí)已經(jīng)創(chuàng)建實(shí)例,不管是否使用都會(huì)創(chuàng)建。
餓漢模式
簡(jiǎn)單餓漢模式的實(shí)現(xiàn)如下,代碼簡(jiǎn)單明了,且沒(méi)有懶漢模式的線程安全問(wèn)題,如不考慮在不使用實(shí)例的情況下占用的那一丟丟內(nèi)存,強(qiáng)烈建議如此使用
public class Singleton {
/**
* 聲明為static成員變量
*/
private static Singleton INSTANCE = new Singleton();
private Singleton() {
//私有構(gòu)造方法
}
public static Singleton getInstance() {
return INSTANCE;
}
}
懶漢模式
餓漢模式就更加值得研究,它雖然在不使用實(shí)例的情況下節(jié)省了系統(tǒng)開(kāi)銷(也只是一丟丟而已),但是同時(shí)帶來(lái)了一些有趣的問(wèn)題。
public class Singleton {
private static Singleton INSTANCE = null;
private Singleton() {
//私有構(gòu)造方法
}
public static Singleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
以上是個(gè)非常簡(jiǎn)單的餓漢模式的實(shí)現(xiàn),有經(jīng)驗(yàn)的猿/媛也會(huì)發(fā)現(xiàn),這個(gè)方法存在線程安全問(wèn)題,當(dāng)多線程并發(fā)調(diào)用getInstance方法時(shí),會(huì)造成創(chuàng)建多次實(shí)例的情況,違背了單例模式設(shè)計(jì)的初衷,于是我們將其更改如下:
public class Singleton {
private static Singleton INSTANCE = null;
private Singleton() {
//私有構(gòu)造方法
}
public static synchronized Singleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
我們?cè)趃etIntance方法上加了synchronized,這樣則快速解決了線程安全問(wèn)題,然而追求完美的我們知道這樣做在高并發(fā)情況下會(huì)發(fā)生阻塞,它還不夠完美(沒(méi)錯(cuò)Coder就是如此的吹毛求疵),我們應(yīng)該縮小synchronized的范圍,于是產(chǎn)生了如下代碼:
public class Singleton {
private volatile static Singleton INSTANCE = null;
private Singleton() {
//私有構(gòu)造方法
}
public static Singleton getInstance() {
if (INSTANCE == null) {
synchronized (Singleton.class) {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
這次我們縮小了synchronized的范圍,且把INSTANCE使用了volatile修飾,是使用雙重check避免了在INSTANCE不為null時(shí)還在等待鎖的問(wèn)題,但是代碼看起來(lái)有點(diǎn)難懂,是不是可能還有更加完美的方式呢,看如下代碼:
public class Singleton {
private Singleton() {
//私有構(gòu)造方法
}
public static Singleton getInstance() {
return SingletonLazyHolder.INSTANCE;
}
private static class SingletonLazyHolder {
private static Singleton INSTANCE = new Singleton();
}
}
這次我們使用了靜態(tài)內(nèi)部類來(lái)實(shí)現(xiàn),根據(jù)Java classloader原理,內(nèi)部類只有在使用時(shí)才會(huì)被初始化,利用這一點(diǎn)可以實(shí)現(xiàn)線程安全的懶漢模式。
另一種模式實(shí)現(xiàn)
以上方式仍然存在問(wèn)題(畢竟我們是吹毛求疵的Coder),因?yàn)樗接邪l(fā)放并不安全,我們?nèi)匀豢梢酝ㄟ^(guò)反射使用AccessibleObject.setAccessible來(lái)創(chuàng)建的實(shí)例,同樣序列化readObject也可以做到創(chuàng)建新的實(shí)例,于是有大神發(fā)明了如下實(shí)現(xiàn)方式:
public class Singleton {
private Singleton() {
//私有構(gòu)造方法
}
public static Singleton getInstance() {
return SingletonEnum.INSTANCE.getInstance();
}
static enum SingletonEnum {
INSTANCE;
private Singleton singleton = null;
private SingletonEnum() {
singleton = new Singleton();
}
public Singleton getInstance() {
return singleton;
}
}
}
利用單元素的枚舉來(lái)實(shí)現(xiàn)單例模式,被稱為“實(shí)現(xiàn)Singleton的最佳方法”,但是否需要如此繁瑣復(fù)雜,需要Coder們自己考慮了。
實(shí)現(xiàn)單例模式并不是我們的目的之一,通過(guò)學(xué)習(xí)不同的實(shí)現(xiàn)方式讓我們對(duì)其他知識(shí)也有了思考,至于選擇哪種實(shí)現(xiàn)方式還是具體問(wèn)題具體分析;如果不是很處女座,餓漢模式是你不悔的選擇!