A5) Singleton(單態(tài)模式)
定義:保證一個(gè)類(lèi)只存在一個(gè)實(shí)例,并提供一個(gè)全局訪(fǎng)問(wèn)的指針。
這個(gè)也是非常常見(jiàn)的模式,可以說(shuō)很難在一個(gè)項(xiàng)目中不使用這個(gè)模式。對(duì)于全局變量來(lái)說(shuō),唯一和同步是至關(guān)重要的,一般來(lái)說(shuō)以下面的這種方式來(lái)實(shí)現(xiàn)。
public class Singleton {
private Singleton(){}
private static Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
}
第一行必須用private的構(gòu)造函數(shù),這樣才能防止別的class生成Singleton的實(shí)例,而第二行的static定義讓變量instance在所有Singleton實(shí)例中是唯一的(當(dāng)然,這個(gè)例子實(shí)際上只有一個(gè)實(shí)例),那么每次通過(guò)getInstance()得到的變量instance就是Singleton的唯一實(shí)例。還有一種方法,如下:
public class Singleton {
private Singleton(){}
private static Singleton instance = null;
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
這種方法叫做lazy initialization技術(shù),不過(guò)關(guān)于這種技術(shù)的爭(zhēng)議很多,牽涉到double-checked locking(DCL)問(wèn)題。關(guān)于這個(gè)問(wèn)題,稍微解釋一下,具體我也不是太明確,有興趣可以另外研究。編譯器在編譯執(zhí)行命令的時(shí)候,由于會(huì)使用代碼優(yōu)化,在synchronized段中的代碼就有可能會(huì)被提前執(zhí)行以加快代碼效率。這個(gè)時(shí)候,也就是instance = new Singleton();這部分語(yǔ)句可能被預(yù)先優(yōu)化執(zhí)行,那么它的唯一性也就不能保證了。雖然還只是在理論上推論這個(gè)結(jié)果,并沒(méi)有用事實(shí)來(lái)驗(yàn)證這一說(shuō)法,現(xiàn)在也沒(méi)發(fā)現(xiàn)這個(gè)方法造成的不良后果,但是,我覺(jué)得還是推薦第一種方法,簡(jiǎn)單而容易理解。 注:感謝rwyx的指點(diǎn),因?yàn)橐郧袄斫庥衅睿詫⑸厦娌糠謩h除后重新改寫(xiě)。
這種方法叫做lazy initialization技術(shù),使用了synchronized關(guān)鍵字,不過(guò)這種方法的系統(tǒng)開(kāi)銷(xiāo)比較大,所以還是推薦地一種方法,簡(jiǎn)單而且效率高,最好在定義時(shí)追加final關(guān)鍵字。
關(guān)于Singleton,在C++/C#等語(yǔ)言中會(huì)用到一種叫做double-checked的技術(shù),目的是為了解決lazy initializtion技術(shù)中同步代碼效率低的問(wèn)題。具體實(shí)現(xiàn)如下:
public static Singleton getInstance() {
if (instance == null) {
synchronized {
if (instance == null) {instance = new Singleton();}
}
}
return instance;
}
即在同步前做一次判斷,這樣只有第一次時(shí)會(huì)進(jìn)入同步段,這樣效率也很高。不過(guò)這種方法在java中卻是錯(cuò)誤的。因?yàn)閖ava語(yǔ)言規(guī)范和C++/C# 等不同,是一個(gè)非常靈活的規(guī)范。java編譯器可以自由地重排變量的初始化和訪(fǎng)問(wèn)順序,以提高運(yùn)行時(shí)效率;同時(shí)java中的變量訪(fǎng)問(wèn)是可以被自動(dòng)緩存到寄存器的,這也導(dǎo)致潛在的JIT 編譯器相關(guān)的依賴(lài)性錯(cuò)誤。每個(gè)編譯器可能有不同的實(shí)現(xiàn)方法,同樣的代碼在不同編譯器和執(zhí)行環(huán)境下可能有不同的表現(xiàn)。解決的辦法有很多,使用線(xiàn)程局部存儲(chǔ)(ThreadLocal)來(lái)保存instance變量,或者使用volatile關(guān)鍵字來(lái)定義instance,強(qiáng)制java編譯器不進(jìn)行優(yōu)化。DCL參考資料: http://www.blogcn.com/User8/flier_lu/blog/1620823.html
參考:
1、 http://www.jdon.com/designpatterns/singleton.htm(中文、java實(shí)例)
2、 http://www.dofactory.com/Patterns/PatternSingleton.aspx(英文、C#實(shí)例、UML)
3、 http://www.techscore.com/tech/DesignPattern/Singleton.html(日文、java實(shí)例、UML)推薦
聯(lián)系客服