單例模式的使用場(chǎng)景:
產(chǎn)生某對(duì)象會(huì)消耗過多的資源定嗓,為避免頻繁地創(chuàng)建與銷毀對(duì)象對(duì)資源的浪費(fèi)枯怖。如:
對(duì)數(shù)據(jù)庫(kù)的操作注整、訪問 IO、線程池度硝、網(wǎng)絡(luò)請(qǐng)求等肿轨。某種類型的對(duì)象應(yīng)該有且只有一個(gè)。如果制造出多個(gè)這樣的實(shí)例蕊程,可能導(dǎo)致:程序行為異常椒袍、資源使用過量、結(jié)果不一致等問題存捺。
單例模式的幾種寫法
- 餓漢槐沼,線程安全
public class Singleton {
public static Singleton instance = new Singleton();
private Singleton (){}
}
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
public class Singleton {
private static Singleton instance = null;
sttaic {
instance = new Singleton();
}
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
這三種方式?jīng)]什么差別曙蒸,都依賴 JVM 在類裝載時(shí)就完成唯一對(duì)象的實(shí)例化,基于類加載的機(jī)制岗钩,它們天生就是線程安全的纽窟,在急切初始化的方案下都是可行的。
- 懶漢兼吓,線程安全
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
這種寫法能夠在多線程中很好的工作臂港,而且看起來它也具備很好的lazy loading,但是视搏,效率很低审孽,只有new對(duì)象的是時(shí)候需要同步,對(duì)象創(chuàng)建好了后再取對(duì)象的時(shí)候是不需要同步的浑娜。
所以我們可以將它改進(jìn)為另一種形式佑力,被稱為“雙重檢查鎖定的方式”
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
這種方法的“雙重檢查”體現(xiàn)在進(jìn)行了兩次 if (singleton == null) 的檢查,這樣既同步代碼塊保證了線程安全筋遭,同時(shí)實(shí)例化的代碼也只會(huì)執(zhí)行一次打颤,實(shí)例化后同步操作不會(huì)再被執(zhí)行,從而效率提升很多
雙重檢查鎖定存在的問題是漓滔,在操作指令重排序的情況下编饺,可能會(huì)導(dǎo)致對(duì)象不唯一 ,所以要在定義單例時(shí)加上 volatile 關(guān)鍵字修飾响驴,保證執(zhí)行的順序透且,就可以使單例起效。
- 靜態(tài)內(nèi)部類
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
這種方式同樣利用了classloder的機(jī)制來保證初始化instance時(shí)只有一個(gè)線程豁鲤,這種方式的 Singleton 類被裝載時(shí)秽誊,只要 SingletonHolder 類還沒有被主動(dòng)使用,instance 就不會(huì)被初始化。只有在顯式調(diào)用 getInstance() 方法時(shí),才會(huì)裝載 SingletonHolder 類矾端,實(shí)例化對(duì)象洞就,實(shí)現(xiàn)了延遲加載。
“靜態(tài)內(nèi)部類”方式與“雙重檢查鎖定”方式相比的優(yōu)勢(shì)在于“雙重檢查鎖定” 方式在 JDK 版本低于 1.5 時(shí)多線程環(huán)境下可能會(huì)失效肾胯,而“靜態(tài)內(nèi)部類”則不受JDK版本的限制竖席。
- 枚舉
public enum Singleton {
INSTANCE;
public void dosomething() {
}
}
這種方式是Effective Java作者Josh Bloch 提倡的方式,它不僅能避免多線程同步問題敬肚,而且還能防止反序列化重新創(chuàng)建新的對(duì)象毕荐。保證了在任何情況(包括反序列化、反射艳馒、克略餮恰)下都是一個(gè)單例员寇,不過由于枚舉是 JDK 1.5 才加入的特性,所以同“雙重檢查鎖定” 方式一樣第美,它對(duì) JDK 的版本也有要求
- 登記式單例——使用 Map 容器來管理單例模式
public class SingletonManager {
private static Map<String, Object> objMap = new HashMap()<String, Object>;
public static void registService(String key, Object instance) {
if(!objMap.containsKey(key))
objMap.put(key, instance);
}
public static Object getService(String key) {
return objMap.getKey();
}
}
在程序的初始蝶锋,我們將一組單例類型注入到一個(gè)統(tǒng)一的管理類中來維護(hù),即將這些實(shí)例存放在一個(gè) Map 登記薄中什往,在使用時(shí)則根據(jù) key 來獲取對(duì)象對(duì)應(yīng)類型的單例對(duì)象扳缕。對(duì)于已經(jīng)登記過的實(shí)例,從 Map 直接返回實(shí)例别威;對(duì)于沒有登記的躯舔,則先登記再返回。從而在對(duì)用戶隱藏具體實(shí)現(xiàn)省古、降低代碼耦合度的同時(shí)粥庄,也降低了用戶的使用成本
需要注意的對(duì)單例模式的破壞
- 如果Singleton實(shí)現(xiàn)了java.io.Serializable接口,那么這個(gè)類的實(shí)例就可能被序列化和復(fù)原豺妓。要避免單例對(duì)象在反序列化時(shí)重新生成對(duì)象惜互,則在 implements Serializable 的同時(shí)應(yīng)該實(shí)現(xiàn) readResolve() 方法,并在其中保證反序列化的時(shí)候獲得原來的對(duì)象:
public class Singleton implements java.io.Serializable {
public static Singleton INSTANCE = new Singleton();
protected Singleton() {
}
private Object readResolve() {
return INSTANCE;
}
}
- 使用反射調(diào)利用私有構(gòu)造器也可以破壞單例科侈,要防止此情況發(fā)生载佳,可以在私有的構(gòu)造器中加一個(gè)判斷,需要?jiǎng)?chuàng)建的對(duì)象不存在就創(chuàng)建臀栈;存在則說明是第二次調(diào)用蔫慧,拋出 RuntimeException 提示。修改私有構(gòu)造函數(shù)代碼如下:
public class Singleton {
...
private Singleton() {
if(instance != null)
throw new RuntimeException("不能創(chuàng)建多個(gè)Singleton對(duì)象");
}
...
}
- 通過克隆來創(chuàng)建一個(gè)新對(duì)象权薯,單例模式就失效了姑躲。單例模式的類是不可以實(shí)現(xiàn) Cloneable 接口的,這與 Singleton 模式的初衷相違背盟蚣。那要如何阻止使用 clone() 方法創(chuàng)建單例實(shí)例的另一個(gè)實(shí)例黍析?可以 override 它的 clone() 方法,使其拋出異常屎开。(也許你想問既然知道了某個(gè)類是單例且單例不應(yīng)該實(shí)現(xiàn) Cloneable 接口阐枣,那不實(shí)現(xiàn)該接口不就可以了嗎?事實(shí)上盡管很少見奄抽,但有時(shí)候單例類可以繼承自其它類蔼两,如果其父類實(shí)現(xiàn)了 clone() 方法的話,就必須在我們的單例類中復(fù)寫 clone() 方法來阻止對(duì)單例的破壞逞度。)
@Override
public class Singleton implements Cloneable {
...
proteced Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
...
}
- 不同的類加載器可以加載同一個(gè)類额划。所以當(dāng)一個(gè)工程下面存在不止一個(gè)類加載器時(shí),整個(gè)程序中同一個(gè)類就可能被加載多次档泽,如果這是個(gè)單例類就會(huì)產(chǎn)生多個(gè)單例并存失效的現(xiàn)象俊戳。因此當(dāng)程序有多個(gè)類加載器又需要實(shí)現(xiàn)單例模式揖赴,就須自行指定類加載器,并要指定同一個(gè)類加載器
private static Class getClass(String classname) throws ClassNotFoundException {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
if(classLoader == null)
classLoader = Singleton.class.getClassLoader();
return (classLoader.loadClass(classname));
}
}