Singleton可能是所有設計模式中最簡(jiǎn)單的設計模式啦。它是如此的簡(jiǎn)單,以至于當我們看著(zhù)它的UML圖的時(shí)候會(huì )為終于沒(méi)有了惱人的連線(xiàn)而雀躍,我們也不用去記憶并體會(huì )它有多么強大的功能、多么高深的實(shí)現技巧。使用它的理由只有一個(gè)――得到一個(gè)Class的唯一的Object。它的實(shí)現是如此簡(jiǎn)單,以至于你會(huì )怪我把它的Code列在下面是在浪費服務(wù)器空間。但是,天下并沒(méi)有這么爽的事兒,當Singleton遇到multi-threading,一些不為人所知的陷阱悄悄的浮上了水面,我們不得不小心應對它們。一下的示例以Java作為編程語(yǔ)言,討論了multi-threading下實(shí)現 Singleton的策略,并順便告訴你幾個(gè)關(guān)于Java實(shí)現Singleton的、你可能不知道的tricky things。
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {}
public static Singleton getInstance(){
if(uniqueInstance == null){
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
這是典型的Singleton的實(shí)現,在single-threading下,它沒(méi)有任何問(wèn)題。但是,你能告訴我在multi-threading下,可能會(huì )發(fā)生什么事情么?multi-threading是頑皮的小孩,它不保證各個(gè)線(xiàn)程執行的順序,所以,會(huì )有一種可能,if (uniqueInstance == null)這個(gè)判斷會(huì )在不同的線(xiàn)程中連續執行。讓我們看看最簡(jiǎn)單的、只有兩個(gè)線(xiàn)程的情況:
thread 1 thread 2 uniqueInstance
if(uniqueInstance == null) null
if(uniqueInstance == null) null
uniqueInstance = new Singleton(); new object
uniqueInstance = new Singleton(); new object
看到了么?產(chǎn)生了兩個(gè)不同的Object!這不一定永遠是壞事,比如這是一個(gè)在線(xiàn)購物的控制系統,那么你可能會(huì )用一件衣服的錢(qián)買(mǎi)到兩件衣服,雖然也有可能是你付了錢(qián),但是沒(méi)得到衣服,但是概率是50%對50%的,也不是什么大不了的事兒~但是,如果這是一個(gè)鐵路調度控制系統呢?火車(chē)也許會(huì )相撞。如果這是一個(gè)核反應堆的控制系統呢?一個(gè)城市可能就變成廢墟了(當然,也許你希望這發(fā)生在日本)。
OK!我們達成了一致,這確實(shí)是個(gè)大問(wèn)題。我們怎么解決它?“我們可以用synchronized!”有人興奮的叫到:“真是個(gè)好主意!”好,我們來(lái)試試看~
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {}
public static synchronized Singleton getInstance(){
if(uniqueInstance == null){
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
好樣的!現在同一時(shí)刻只有一個(gè)線(xiàn)程能調用getInstance()方法,不會(huì )存在城市被炸平的悲劇了,我們拯救了幾百萬(wàn)人(上海的話(huà),一千多萬(wàn)人)!我代表他們感謝你,但是,過(guò)不了多久就有人會(huì )站出來(lái)抱怨:“為什么系統的速度變慢了?”這些沒(méi)良心的人們,這么快就忘記了這段code拯救了他們的性命,竟然抱怨起了這么小的問(wèn)題!憤憤之余,你開(kāi)始思考速度變慢的原因。
啊,synchronized,它使得原本并行的訪(fǎng)問(wèn)變成了串行,這是性能的瓶頸,有什么辦法可以解決嗎?當然,有人站了出來(lái):“我有投機取巧的方法?!?br> public class Singleton {
private static Singleton uniqueInstance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return uniqueInstance;
}
}
問(wèn)題解決了~JVM會(huì )在所有線(xiàn)程之前先實(shí)例化一個(gè)Object,所有的getInstance()調用都是在引用這個(gè)預先產(chǎn)生的Object,不會(huì )有任何的問(wèn)題啦~
但是,完美主義的我們并不滿(mǎn)意:“無(wú)論是否使用這個(gè)Class,都會(huì )預先產(chǎn)生一個(gè)Object,這很不優(yōu)雅!”那我們就看看優(yōu)雅的解決方案??紤]一下 multi-threading下出現錯誤的原因,并不是因為不同的線(xiàn)程同時(shí)調用了getInstance()方法,而是if (uniqueInstance == null)出現了意想不到的排列,也就是說(shuō),只有在產(chǎn)生Object的時(shí)候,才會(huì )有multi-threading的問(wèn)題,當 uniqueInstance != null時(shí),多少個(gè)線(xiàn)程都不會(huì )有問(wèn)題。因此,我們得到了結論:只需要對創(chuàng )建Object的code上鎖就可以了。
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {}
public static Singleton getInstance() {
if (uniqueInstance == null) {
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
關(guān)鍵字volatile可以保證uniqueInstance可以被同時(shí)運行的若干個(gè)線(xiàn)程正確的修改,而getInstance()方法體中的寫(xiě)法則保證了細力度的線(xiàn)程鎖。注意,uniqueInstance == null被判斷了兩次,這是必要的,這種方法叫做Double-checked locking。
噹噹噹噹~任務(wù)完成,Singleton不在懼怕multi-threading啦!讓我們來(lái)看看關(guān)于Java實(shí)現Singleton的tricky things。 - Double-checked locking最好只在Java 5中使用。在Java 1.4以及之前版本的諸多實(shí)現中,volatile關(guān)鍵字都被錯誤的實(shí)現,這會(huì )導致Double-checked locking的不正確執行。
- 在Java 1.2以及之前的版本中,JVM的垃圾回收器存在一個(gè)bug:當一個(gè)Singleton的Object不存在全局引用(即只有自身的instance變量 在引用這Object)的時(shí)候,垃圾回收器會(huì )回收這個(gè)Object。也就是說(shuō),在這種情況下,getIntance()會(huì )得到一個(gè)新的Object,這不會(huì )導致城市被炸平,但是會(huì )導致做了10年的核反應成果突然消失,當然不是我們想要的。
- Java允許不同的Class Loader創(chuàng )建自己的Object,它們用namespace來(lái)區別這些Object。因此,當你的Singleton設計Class Loader時(shí),最好在獲取Object的時(shí)候同時(shí)指定它的Class Loader。