定義:
保證一個(gè)類僅有一個(gè)實(shí)例,并提供一個(gè)訪問它的全局訪問點(diǎn)。
1. 餓漢模式
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
這種方式在類加載時(shí)就完成了初始化,所以類加載較慢,但獲取對(duì)象的速度快。 這種方式基于類加載機(jī)制避免了多線程的同步問題。
2. 懶漢模式(線程不安全)
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
懶漢模式申明了一個(gè)靜態(tài)對(duì)象,在用戶第一次調(diào)用時(shí)初始化,雖然節(jié)約了資源,但第一次加載時(shí)需要實(shí)例化,反映稍慢一些,而且在多線程不能正常工作。
3. 懶漢模式(線程安全)
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
這種寫法能夠在多線程中很好的工作,但是每次調(diào)用getInstance方法時(shí)都需要進(jìn)行同步,造成不必要的同步開銷,而且大部分時(shí)候我們是用不到同步的,所以不建議用這種模式。
4. 雙重檢查模式 (DCL)
public class Singleton {
private volatile static Singleton singleton;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return singleton;
}
}
這種寫法在getSingleton方法中對(duì)singleton進(jìn)行了兩次判空,第一次是為了不必要的同步,第二次是在singleton等于null的情況下才創(chuàng)建實(shí)例。
在這里使用volatile會(huì)或多或少的影響性能,但考慮到程序的正確性,犧牲這點(diǎn)性能還是值得的。 DCL優(yōu)點(diǎn)是資源利用率高,第一次執(zhí)行g(shù)etInstance時(shí)單例對(duì)象才被實(shí)例化,效率高。缺點(diǎn)是第一次加載時(shí)反應(yīng)稍慢一些,在高并發(fā)環(huán)境下也有一定的缺陷,雖然發(fā)生的概率很小。DCL雖然在一定程度解決了資源的消耗和多余的同步,線程安全等問題,但是他還是在某些情況會(huì)出現(xiàn)失效的問題,也就是DCL失效,在《java并發(fā)編程實(shí)踐》一書建議用靜態(tài)內(nèi)部類單例模式來替代DCL。
5. 靜態(tài)內(nèi)部類單例模式
public class Singleton {
private Singleton() {
}
public static Singleton getInstance() {
return SingletonHolder.sInstance;
}
private static class SingletonHolder {
private static final Singleton sInstance = new Singleton();
}
}
第一次加載Singleton類時(shí)并不會(huì)初始化sInstance,只有第一次調(diào)用getInstance方法時(shí)虛擬機(jī)加載SingletonHolder 并初始化sInstance ,這樣不僅能確保線程安全也能保證Singleton類的唯一性,所以推薦使用靜態(tài)內(nèi)部類單例模式。
6. 枚舉單例
public enum Singleton {INSTANCE;
public void doSomeThing() {
}
}
默認(rèn)枚舉實(shí)例的創(chuàng)建是線程安全的,并且在任何情況下都是單例,上述講的幾種單例模式實(shí)現(xiàn)中,有一種情況下他們會(huì)重新創(chuàng)建對(duì)象,那就是反序列化,將一個(gè)單例實(shí)例對(duì)象寫到磁盤再讀回來,從而獲得了一個(gè)實(shí)例。反序列化操作提供了readResolve方法,這個(gè)方法可以讓開發(fā)人員控制對(duì)象的反序列化。在上述的幾個(gè)方法示例中如果要杜絕單例對(duì)象被反序列化是重新生成對(duì)象,就必須加入如下方法:
private Object readResolve() throws ObjectStreamException{
return singleton;
}
枚舉單例的優(yōu)點(diǎn)就是簡(jiǎn)單,但是大部分應(yīng)用開發(fā)很少用枚舉,可讀性并不是很高,不建議用。
7. 使用容器實(shí)現(xiàn)單例模式
public class SingletonManager {
private Map<String, Object> objMap = new HashMap<String, Object>();
public static Object getService(String key) {
return objMap.get(key);
}
public static void registerService(String key, Object instance) {
if (!objMap.containsKey(key)) {
objMap.put(key, instance);
}
}
}
用SingletonManager 將多種的單例類統(tǒng)一管理,在使用時(shí)根據(jù)key獲取對(duì)象對(duì)應(yīng)類型的對(duì)象。這種方式使得我們可以管理多種類型的單例,并且在使用時(shí)可以通過統(tǒng)一的接口進(jìn)行獲取操作,降低了用戶的使用成本,也對(duì)用戶隱藏了具體實(shí)現(xiàn),降低了耦合度。
總結(jié)
到這里七種寫法都介紹完了,至于選擇用哪種形式的單例模式,取決于你的項(xiàng)目本身,是否是有復(fù)雜的并發(fā)環(huán)境,還是需要控制單例對(duì)象的資源消耗。