7種Java單例模式__Java

來源:互聯網
上載者:User
單例模式 - 終極篇 1.  前言

單例(Singleton)是設計模式當中使用比較常用和重要的一種模式,有些架構師並不把單例作為一種設計模式,而是一種實現方式。下面是我自己總結的7中單例模式的寫法,廢話不多說,直接上代碼:(分享註明出處即可,看完這一篇基本上不用再看其他亂起八糟的總結了。) 2. 什麼是單例。

單例對象的類必須保證只有一個執行個體存在(from wiki) 懶漢式:lazy load 第一種(懶漢式簡單版):

public class Single1 {private static Single1 instance;//此處一定是私人//public Single1(){}  省略預設構造public static Single1 getInstance(){if (instance==null) {instance=new Single1();}return instance;}}

很明顯這是一種有缺陷的寫法,初步最佳化,是將構造器私人,這樣可以防止被外部的類調用。

1.//ViViD  2.public class Singleton {  3.    private static Singleton instance = null;  4.  5.    private Singleton() {  6.    }  7.  8.    public static Singleton getInstance() {  9.        if (instance == null) {  10.            instance = new Singleton();  11.        }  12.        return instance;  13.    }  14.}  

這種寫法在大多數的時候也是沒問題的。雖然具備lazyloding,但致命缺點:多線程下不能正常工作。這種寫法能夠在多線程中很好的工作,但是,遺憾的是,效率很低,99%情況下不需要同步。問題在於,當多線程工作的時候,如果有多個線程同時運行到if (instance == null),都判斷為null,那麼兩個線程就各自會建立一個執行個體——這樣一來,就不是單例了。 第二種(懶漢,安全執行緒):

1.public class Singleton {  2.    private static Singleton instance = null;  3.  4.    private Singleton() {  5.    }  6.  7.    public static Synchronized Singleton getInstance() {  8.        if (instance == null) {  9.            instance = new Singleton();  10.        }  11.        return instance;  12.    }  13.}

這種寫法能夠在多線程中很好的工作,而且看起來它也具備很好的lazy loading,但是,遺憾的是,效率很低,99%情況下不需要同步。

加上synchronized關鍵字之後,getInstance方法就會鎖上了。如果有兩個線程(T1、T2)同時執行到這個方法時,會有其中一個線程T1獲得同步鎖,得以繼續執行,而另一個線程T2則需要等待,當第T1執行完畢getInstance之後(完成了null判斷、對象建立、獲得傳回值之後),T2線程才會執行執行。——所以這端代碼也就避免了第二種中,可能出現因為多線程導致多個執行個體的情況。
但是,這種寫法也有一個問題:給gitInstance方法加鎖,雖然會避免了可能會出現的多個執行個體問題,但是會強制除T1之外的所有線程等待,實際上會對程式的執行效率造成負面影響。 第三種( Double-Check雙重檢驗鎖 ) Version2代碼相對於Version1d代碼的效率問題,其實是為瞭解決1%幾率的問題,而使用了一個100%出現的防護盾。那有一個最佳化的思路,就是把100%出現的防護盾,也改為1%的幾率出現,使之只出現在可能會導致多個執行個體出現的地方。 JDK1.5 以後源碼裡就是這樣寫的。
——有沒有這樣的方法呢。當然是有的,改進後的代碼Vsersion3如下:

1.// 帶雙重檢測鎖的單例  2.public class Singleton {  3.    private static Singleton5 instance = null;  4.  5.    private Singleton() {  6.    }  7.  8.    public static Singleton getInstance() {  9.        if (instance == null) {  10.            synchronized (Singleton.class) {  11.                if (instance == null) {  12.                    instance = new Singleton();  13.                }  14.            }  15.        }  16.        return instance;  17.    }  18.}  

這個是第二種方式的升級版,俗稱雙重檢查鎖定,詳細介紹請查看:JDK源碼。

在JDK1.5之後,雙重檢查鎖定才能夠正常達到單例效果。

這個版本的代碼看起來有點複雜,注意其中有兩次if (instance == null)的判斷,這個叫做『雙重檢查Double-Check』。

·        第一個if (instance == null),其實是為瞭解決Version2中的效率問題,只有instance為null的時候,才進入synchronized的程式碼片段——大大減少了幾率。

·        第二個if (instance == null),則是跟Version2一樣,是為了防止可能出現多個執行個體的情況。

—— 這段代碼看起來已經很完美了。
—— 當然,只是『看起來』,還是有小機率出現問題的。
這弄清楚為什麼這裡可能出現問題,首先,我們需要弄清楚幾個概念:原子操作、指令重排。 餓漢式:eagerly load

餓漢式單例是指:指全域的單例執行個體在類裝載時構建的實現方式。由於類裝載的過程是由類載入器(ClassLoader)來執行的,這個過程也是由JVM來保證同步的,所以這種方式先天就有一個優勢——能夠免疫許多由多線程引起的問題。 第四種(餓漢,線程不安全):

1.public class Singleton {  2.    //2、自訂一個本類對象。  3.    private static Singleton1 intance = new Singleton();  4.    //1、私人化建構函式。  5.    private Singleton() {  6.    }  7.    //3、定義一個方法返回改對象。讓其他程式通過這個方法就可以擷取該對象。  8.    public static Singleton getInstance() {  9.        return intance;  10.    }  11.}  

第五種(靜態內部類):
1.public class Singleton5 {  2.    private Singleton5() {}  3.  4.    private static class SingletonHolder {  5.        private static final Singleton5 INSTANCE = new Singleton7();  6.    }  7.  8.    public static final Singleton5 getInstance() {  9.        return SingletonHolder.INSTANCE;  10.    }  11.}  

這種方式同樣利用了 classloder 的機制來保證初始化 instance 時只有一個線程,它跟第三種和第四種方式不同的是(很細微的差別):第三種和第四種方式是只要 Singleton 類被裝載了,那麼 instance 就會被執行個體化(沒有達到 lazy loading 效果),而這種方式是 Singleton 類被裝載了, instance 不一定被初始化。因為 SingletonHolder 類沒有被主動使用,只有顯示通過調用 getInstance 方法時,才會顯示裝載 SingletonHolder 類,從而執行個體化 instance 。想象一下,如果執行個體化 instance 很消耗資源,我想讓他消極式載入,另外一方面,我不希望在 Singleton 類載入時就執行個體化,因為我不能確保 Singleton 類還可能在其他的地方被主動使用從而被載入,那麼這個時候執行個體化 instance 顯然是不合適的。這個時候,這種方式相比第三和第四種方式就顯得很合理。

第六種(枚舉優雅版):

1.public enum Singleton {    2.    INSTANCE;    3.    public void whateverMethod() {    4.    }   5.}    

這是一個枚舉類型……連class都不用了,極簡。使用時可以直接Singleton.INSTANCE. whateverMethod();由於建立枚舉執行個體的過程是安全執行緒的,所以這種寫法也沒有同步的問題。

這種方式是Effective Java作者Josh Bloch 提倡的方式,它不僅能避免多線程同步問題,而且還能防止還原序列化重新建立新的對象。個人認為由於1.5中才加入enum特性,普及率不夠高,在實際工作中,發現被使用的不是很廣泛。

作者對這個方法的評價:

這種寫法在功能上與共有域方法相近,但是它更簡潔,無償地提供了序列化機制,絕對防止對此執行個體化,即使是在面對複雜的序列化或者反射攻擊的時候。雖然這中方法還沒有廣泛採用,但是單元素的枚舉類型已經成為實現Singleton的最佳方法。

枚舉單例這種方法問世一些,許多分析文章都稱它是實現單例的最完美方法——寫法超級簡單,而且又能解決大部分的問題。
不過我個人認為這種方法雖然很優秀,但是它仍然不是完美的——比如,在需要繼承的情境,它就不適用了。 第七種終極版 (volatile)

對於Double-Check這種可能出現的問題(當然這種機率已經非常小了,但畢竟還是有的嘛~),解決方案是:只需要給instance的聲明加上volatile關鍵字即可,volatile版本如下:

1.public class Singleton  2.{  3.    private volatile static Singleton singleton = null;  4.    private Singleton()  {    }  5.    public static Singleton getInstance()   {  6.        if (singleton== null)  {  7.            synchronized (Singleton.class) {  8.                if (singleton== null)  {  9.                    singleton= new Singleton();  10.                }  11.            }  12.        }  13.        return singleton;  14.    }  15.} 

   volatile關鍵字的一個作用是禁止指令重排,把instance聲明為volatile之後,對它的寫操作就會有一個記憶體屏障(什麼是記憶體屏障。),這樣,在它的賦值完成之前,就不用會調用讀操作。

    注意:volatile阻止的不singleton = newSingleton()這句話內部[1-2-3]的指令重排,而是保證了在一個寫操作([1-2-3])完成之前,不會調用讀操作(if (instance == null))。

  ——也就徹底防止了Version3中的問題發生。
——好了,現在徹底沒什麼問題了吧。
……
……
……
好了,別緊張,的確沒問題了。大名鼎鼎的EventBus中,其入口方法EventBus.getDefault()就是用這種方法來實現的

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.