單例模式的幾種實現方式

來源:互聯網
上載者:User

標籤: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; }
}

 




 

單例模式的幾種實現方式

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.