標籤:style blog color io 使用 java strong for div
單例模式是現如今非常普遍的模式之一。它是一種對象建立模式,用於生產一個對象的具體執行個體,它可以確保一個系統中一個類只產生一個執行個體。在java中,這樣的行為帶來兩種好處:
1):對於頻繁建立的對象,可以省略對象建立所花費的時間,對於一些重量級對象而言,是非常可觀的系統開銷。
2):由於new的操作減少,因而對系統記憶體的使用頻率也會降低,這將減輕GC的壓力,縮短GC的停頓時間。
因此對於系統的關鍵組件和被頻繁使用的對象,使用單例模式可以有效改善系統效能。
下面介紹幾種單例模式的寫法:
1.餓漢模式
public Singleton { private Singleton { System.out.println("Singleton is create")//建立單例的過程可能會比較慢 } private static Singleton instance = new Singleton(); public static Singleton getInstance() { return instance; }}
這是最為普遍的一種方法,簡單易懂。注意代碼中重點標註的部分,首先單例必須要有一個private存取層級的建構函式。只有這樣才能保證不會在系統中其他代碼中執行個體化,這點是相當重要的;其次instance成員變數和getInstance()方法必須是static的。使用者通過Singleton.getInstance()就可以獲得相關單例(類載入時建立instance對象建立於靜態記憶體區內並且只有一個拷貝。在這裡注意靜態方法獨立於任何執行個體,因此static方法必須被實現,而不能是抽象的abstract。而且因為執行個體成員與特定的對象關聯,只能訪問所屬類的靜態成員變數和成員方法。)。
不過它的不足也顯而易見,就是無法對instance執行個體做延時載入。假如單例的建立過程很慢,而由於instance成員變數時static定義的,因此JVM載入單例類時,單例對象就會在靜態記憶體區裡建立。如果此時此單例在系統中還扮演其他角色(就是用到其他靜態方法或變數),那麼任何使用這個單例類的時候都會初始化這個單例變數instance,而不管是否會被用到(即任何Singleton.otherStaticMethod()時候,都會執行其構造方法輸出"Singleton is create"而造成不必要的時間和記憶體的開銷)。為瞭解決這個問題我們需要引入一個消極式載入機制,就是下面的懶漢模式。
2.懶漢模式
public LazySingleton { private LazySingleton { System.out.println("LazySingleton is create")//建立單例的過程可能會比較慢 } private static LazySingleton instance = null; public static LazySingleton getInstanceNotSafe() {//線程不安全 if(instance = null) { instance = new LazySingleton; } return instance; } public static synchronized LazySingleton getInstanceSafe() {//安全執行緒 if(instance = null) { instance = new LazySingleton; } return instance; }}
這裡首先對於靜態變數instance初始值賦予null,確保類載入時沒有額外的負載。其次在getInstanceFactory 方法中,判斷當前單例是否存在,若存在則返回不存在時再建立單例。注意上述中getInstanceNodSafe()方法不是同步的,故在多線程的環境下線程1正建立單例時,完成賦值操作,這時線程2判斷instance為null,故線程2也將建立單例的程式,而導致多個執行個體被建立而導致非單例。所以在多線程的環境中使用引入同步關鍵字的getInstanceSafe()的方法,但是因為同步需要等待它的時耗遠遠大於餓漢模式。
以下測試代碼說明了這個問題:
public void run() { for(int i = 0 ;i < 100000 ;i++) { Singleton.getInstance(); //LazySingleton.getInstanceSafe(); } System.out.println("spent:" + (System.currentTimeMillis() - begintime)); }
開啟五個線程同時完成以上代碼,餓漢模式的單例耗時0ms,而使用LazySingleton卻相對耗時約390ms。效能上至少相差兩個數量級。為了消極式載入引入了同步關鍵字後降低了效能,為使這個同步方法更為有效一個雙重檢查鎖定的模式應運而生了。
3.雙重檢查鎖定模式
public Singleton { private Singleton { System.out.println("Singleton is create")//建立單例的過程可能會比較慢 } private static Singleton instance = null; public static Singleton getInstance() { if (instance == null) { synchronized(Singleton.class) { if(instance == null) { instance = new Singleton(); } } } return instance; }}
此模式先判斷instance是否為null,之後才進入同步語句。當第一個線程1與第二個線程2並發進入第一個if語句後,一個線程進入synchronized塊來初始化instance而另一個線程則被阻斷。當第一個線程退出synchronized塊時,等待著的線程2進入並判斷instance是否已建立再返回instance。至此對instance進行兩次檢查。這也是“雙重檢查鎖定”名稱的由來。與懶漢模式相比不用每次調用getinstance()都付出同步的代價,只有第一次建立才會同步,建立之後就沒用了。
4.靜態內部類模式
public class StaticInnerClassSingleton { private StaticInnerClassSingleton () { System.out.println("StaticInnerClassSingleton is create"); } private static class SingletonHolder { private static StaticInnerClassSingleton instance = new StaticInnerClassSingleton();//只會載入一次得到單個執行個體 } public static StaticInnerClassSingleton getInstance() { return SingletonHolder.instance; }
}
在這個實現中,單例模式內部類來維護單例的執行個體。當StaticInnerClassSingleton載入時,其內部類不會被初始化,故StaticInnerClassSingleton類被載入JVM時,不會初始化單例類,而當getInstance()方法被調用時,才會載入SingletonHolder,從而初始化。同時,由於執行個體建立是在內部類載入時完成,故天生對多線程友好。getInstance()方法也不需要使用同步關鍵字。因此與餓漢模式相比等待消極式載入instance,所以不用擔心只想調用其他靜態方法時會建立一個單例。
5.總結與漏洞修複
通常情況下,用以上方式實現單例已經可以確保在系統中只存在唯一的執行個體,個人比較推薦用第三或第四種方法。但仍然有例外的情況,可能導致系統產生多個執行個體,比如在代碼中,通過反射機制,強行調用單例類的私人建構函式產生多個單例,但是我們在這裡先不討論這種極端方式。但是仍有些合法的方法可能導致多個單例的產生,如:
1.如果單例由不同的類裝載器裝入,那便有可能存在多個單例類的執行個體。假定不是遠端存取,例如一些servlet容器對每個servlet使用完全不同的類裝載器,這樣的話如果有兩個servlet訪問一個單例類,它們就都會有各自的執行個體。
2.如果Singleton實現了java.io.Serializable介面,那麼這個類的執行個體就可能被序列化和複原。不管怎樣,如果你序列化一個單例類的對象,接下來複原多個那個對象,那你就會有多個單例類的執行個體。
對於第一個問題修複方法:
private static Class getClass(String classname) throws ClassNotFoundException {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); if(classLoader == null){ classLoader = Singleton.class.getClassLoader(); }
return (classLoader.loadClass(classname));
} }
對於第二個問題的修複方法:
public class Singleton implements java.io.Serializable {
public static Singleton instance = new Singleton(); protected Singleton() { System.out.println("Singleton is create"); }
private Object readResolve() { //這裡修複,阻止產生新的執行個體總是返回當前對象 return instance; }
}
單例模式的幾種實現方式