單例設計模式是23種設計模式中,最基礎也是最經常使用的設計模式之一,也是面試中關於設計模式知識點考察比較高頻的問題之一。提及單例模式的寫法,大多數狀況下出如今咱們腦海中的可能就是「餓漢式」,「懶漢式」這兩種寫法,可是今天小碼哥今天要介紹的是單例模式的7種寫法,之後面試官要是再問你單例模式,那就拋給他這七種寫法吧!面試
接下來,咱們就言歸正傳,來一一介紹這七種單例模式的寫法吧!設計模式
一、餓漢式安全
餓漢式是單例模式設計中比較經典的實現方式。實現代碼以下:多線程
//final不容許被繼承 public final class SingleTonEhangshi { //實例變量 private byte[] data = new byte[1024]; //在定義實例對象時直接初始化 private static SingleTonEhangshi instance = new SingleTonEhangshi(); //私有化構造函數,不容許外部NEW private SingleTonEhangshi() { } public static SingleTonEhangshi getInstance() { return instance; } }
餓漢式的實現關鍵在於instance做爲類變量直接獲得了初始化,若是咱們主動使用SingleToEhangshi類,那麼instance實例將會直接完成建立,包括其中的實例變量也都會獲得初始化。框架
instance做爲類變量,在類初始化的過程當中會被收集進<clinit>()方法中,而該方法是能夠100%地保證同步,也就是說instance在多線程的狀況下不可能被初始化兩次。可是因爲instance被ClassLoader加載後很長一段時間才被使用的話,那就會意味着instance實例所開闢的堆內存會駐留很長的時間。socket
整體說來,若是一個類中的成員變量比較少,且佔用的內存資源也很少,用餓漢式的方式實現單例模式也何嘗不可,只是其沒法進行懶加載。ide
二、懶漢式函數
所謂懶漢式就是在使用類實例的時候再去建立,也就是說用到的時候我再建立,這樣就能夠避免類在初始化的時候提早建立過早地佔用內存空間。實現代碼以下:性能
//final不容許被繼承 public final class SingleTonLhangshi { //實例變量 private byte[] data = new byte[1024]; //定義實例,可是不直接初始化 private static SingleTonLhangshi instance = null; //私有化構造函數,不容許外部NEW private SingleTonLhangshi() { } public static SingleTonLhangshi getInstance() { if (null == instance) { instance = new SingleTonLhangshi(); } return instance; } }
類變量instance=null,所以當類被初始化的時候instance並不會馬上被實例化,而是在getInstance()方法被調用時判斷instance實例是否被實例化,若是沒有實例化在調用私有構造方法進行實例化操做。this
懶漢式寫法在多線程環境下,會存在同一時間多個線程同時看到null==instance的狀況,從而致使instance會被實例化屢次,從而沒法保證單例的惟一性。
三、懶漢式+同步方法
懶漢式的單例實現方式能夠保證明例的懶加載,可是卻沒法保證明例的惟一性。在多線程環境下因爲instance爲共享數據,當多個線程訪問使用時,須要保證數據的同步性,因此若是須要保證懶漢式實例的惟一性,咱們能夠經過同步的方式來實現。代碼以下:
/final不容許被繼承 public final class SingleTonLhangshiSync { //實例變量 private byte[] data = new byte[1024]; //定義實例,可是不直接初始化 private static SingleTonLhangshiSync instance = null; //私有化構造函數,不容許外部NEW private SingleTonLhangshiSync() { } //向getInstance方法加入同步控制,每次只能有一個線程可以進入 public static synchronized SingleTonLhangshiSync getInstance() { if (null == instance) { instance = new SingleTonLhangshiSync(); } return instance; } }
採用懶漢式+數據同步的方法既知足了懶加載又可以100%保證instance實例的惟一性。可是,synchronized關鍵字的排它性會致使getInstance()方法同一時刻只能被一個線程訪問,性能會比較低下。
四、Double-Check
Double-Check是一種比較聰明的設計方式,它提供了一種高效的數據同步策略,那就是首次初始化的時候加鎖,以後則容許多個線程同時進行getInstance()方法的調用來得到類的實例。代碼以下:
//final不容許被繼承 public final class SingletonDoubleCheck { //實例變量 private byte[] data = new byte[1024]; //定義實例,可是不直接初始化 private static SingletonDoubleCheck instance = null; Connection con; Socket socket; //私有化構造函數,不容許外部NEW private SingletonDoubleCheck() { this.con = con;//初始化 this.socket = socket;//初始化 } public static SingletonDoubleCheck getInstance() { //當instance爲null時,進入同步代碼塊,同時該判斷避免了每次都須要進入同步代碼塊,能夠提升效率 if (null == instance) { //只有一個線程可以得到SingletonDoubleCheck.class關聯的monitor synchronized (SingletonDoubleCheck.class) { //判斷若是instance爲null則建立 if (null == instance) { instance = new SingletonDoubleCheck(); } } } return instance; } }
當兩個線程發現null==instance成立時,只有一個線程有資格進入同步代碼塊,完成對instance的初始化,隨後的線程發現null==instance不成立則無須進行任何操做,之後對getInstance的訪問就不會再須要進行數據同步了。
此種方式看起來是既知足了懶加載,又保證了instance實例的惟一性,而且還提供了比較高效的數據同步策略,能夠容許多個線程同時對getInstance進行訪問。可是這種方式在多線程的狀況下,可能會引發空指針異常,這是由於若是在如上代碼的構造方法中還存在初始化其餘資源的狀況的話,因爲JVM運行時存在指令重排的狀況,這些資源在實例化時順序並沒有先後關係的約束,那麼在這種狀況下,就極有多是instance最早被實例化,而con和socket並未完成實例化,而未完成實例化的實例在調用其方法時將會拋出空指針異常。
五、Volatile+Double-Check
爲了解決Double-Check指令重排致使的空指針問題,能夠用volatile關鍵字防止這種重排序的發生。所以代碼只須要稍做修改就能知足多線程下的單例、懶加載以及實例的高效性了。代碼以下:
//final不容許被繼承 public final class SingletonDoubleCheck { //實例變量 private byte[] data = new byte[1024]; //定義實例,可是不直接初始化 private static volatile SingletonDoubleCheck instance = null; Connection con; Socket socket; //私有化構造函數,不容許外部NEW private SingletonDoubleCheck() { this.con = con;//初始化 this.socket = socket;//初始化 } public static SingletonDoubleCheck getInstance() { //當instance爲null時,進入同步代碼塊,同時該判斷避免了每次都須要進入同步代碼塊,能夠提升效率 if (null == instance) { //只有一個線程可以得到SingletonDoubleCheck.class關聯的monitor synchronized (SingletonDoubleCheck.class) { //判斷若是instance爲null則建立 if (null == instance) { instance = new SingletonDoubleCheck(); } } } return instance; } }
六、Holder方式
Holder方式徹底藉助了類加載的特色。代碼以下:
//不容許被繼承 public final class SingletonHolder { //實例變量 private byte[] data = new byte[1024]; private SingletonHolder() { } //在靜態內部類中持有單例類的實例,而且可直接被初始化 private static class Holder { private static SingletonHolder instance = new SingletonHolder(); } //調用getInstance方法,事實上是得到Holder的instance靜態屬性 public static SingletonHolder getInstance() { return Holder.instance; } }
在單例類中並無instance的靜態成員,而是將其放到了靜態內部類Holder之中,所以單例類在初始化的過程當中並不會建立SingletonHolder的實例,內部類Holder中定義了SingletonHolder的靜態變量,而且直接進行了實例化,只有當Holder被主動引用的時候纔會建立SingletonHolder的實例。
SingletonHolder實例的建立過程在Java程序編譯時期收集至<clinit>()方法中,該方法又是同步方法,能夠保證內存的可見性、JVM指令的順序性和原子性。Holder方式的單例模式設計是最好的設計之一,也是目前使用比較廣的設計。
七、枚舉方式
枚舉方式在不少開源框架中也應用得比較普遍,枚舉類型不容許被繼承,一樣是線程安全的,而且只能被實例化一次,可是枚舉類型不可以實現懶加載。用枚舉類型,實現單例模式的代碼以下:
public class SingletonEnum { //實例變量 private byte[] data = new byte[1024]; private SingletonEnum() { } //使用枚舉充當Holder private enum EnumHolder { INSTANCE; private SingletonEnum instance; EnumHolder() { this.instance = new SingletonEnum(); } private SingletonEnum getInstance() { return instance; } } public static SingletonEnum getInstance() { return EnumHolder.INSTANCE.getInstance(); } }
以上就是要給你們介紹的單例模式的7種寫法了,雖然單例模式很是簡單,可是在多線程的狀況下,咱們以前所設計的單例程序未必可以知足單實例、懶加載以及高性能的特色。