設計模式之單列模式 1,何為單列模式? 即singleton 在某個類採用了單列模式之後 其只能有一個實列對象 ,並且這個實列對象只能有內部自己創建並提供給外部的調用。 2.實現單列模式的方法 分為 :餓漢式 ,懶漢式 下麵為餓漢式實現代碼: 餓漢式 通過創建一個靜態成員變數 在類載入的時候直接創建 ...
設計模式之單列模式
1,何為單列模式?
即singleton 在某個類採用了單列模式之後 其只能有一個實列對象 ,並且這個實列對象只能有內部自己創建並提供給外部的調用。
2.實現單列模式的方法
分為 :餓漢式 ,懶漢式
下麵為餓漢式實現代碼:
public calss Singleton1{ //將構造函數私有化 防止外部通過new來創建對象 private Singleton1(){ } //創建一個私有靜態變數並直接初始化 類載入的時候直接創建對象 private static Singleton1 single = new Singleton(); //提供一個public方法 來讓外部調用 返回已構建的私有single public static Singleton1 getInstance(){ return single; } }
餓漢式 通過創建一個靜態成員變數 在類載入的時候直接創建 該類的對象,所以其天生就是線程安全的。但是缺點在於 無論其是否被使用 記憶體已經為其分配好記憶體空間,所以其缺點就是對於有記憶體要求的餓漢式比較消耗記憶體。
懶漢式具體實現:
public class Singleton2{ //私有化構造方法 private Singleton2(){ } // 定義靜態私有成員變數 但不初始化 private static Singleton2 single = null; //提供public方法給外部用來獲取實列對象 public static Singleton2 (){ if(single==null){ single = new Singleton2(); } return single } }View Code
這是懶漢式的基本實現方式,但其對於多線程是不安全的,因為當兩個線程同時進入getInstance方法時 線上程1經過single==null判斷後 ,對single進行初始化而並未完成初始化,線程2進入single==null判斷 由於線程1還未完成初始化 所以此時的single線上程2看來仍然是null,所以線程2也進入了對single進行初始化的代碼段,從而出現了兩個single對象 ,這與單列模式的要求是不符的,所以為了達到線程安全 下麵給出了三種方法,其中第三種方法在某些對單列模式的介紹中 單一分離了出來,但我認為其仍然屬於懶漢式,因為在類的載入過程中該類的對象並未被創建,同樣是在使用的時候才被創建。
1.通過synchronized同步化 實現線程安全
public calss Singleton3{ //將構造函數私有化 防止外部通過new來創建對象 private Singleton3(){ } //創建一個私有靜態變數 占不創建對象 private static Singleton3 single = null; //提供一個public方法 並加上synchronized關鍵字 同步化來讓外部調用 返回已構建的私有single 保證線程安全 public static synchronized Singleton3 getInstance(){
if(single==null){ single=new Singleton3(); }
return single; } }
這種方法雖然實現了線程安全,但是由於synchronized的同步化導致其效率會降低,
2.通過雙重鎖定實現單列模式的線程安全
public calss Singleton4{ //將構造函數私有化 防止外部通過new來創建對象 private Singleton4(){ } //創建一個私有靜態變數 占不創建對象 private static volatile Singleton4 single = null; //提供一個public方法 並加上synchronized關鍵字 同步化來讓外部調用 返回已構建的私有single 保證線程安全 public static Singleton4 getInstance(){ if(single==null){ // 這裡面的Singleton4.class可以為任何一個不變的值,它只是一個標識,用來給線程看的 synchronized(Singleton4.class){ if(single==null){ single=new Singleton4(); } } } return single; } }
這種方法實現單列模式 保證了線程的安全性同時提高了效率,而且節約記憶體空間,只有在第一次調用的時候才會創建對象。
在創建靜態變數single時 所加的關鍵字volatile, 因為構造函數大致可被分為兩部分 一是先分配記憶體並複製 二然後再初始化 所以不加volatile可能導致雖然修改了single的值但是並沒有對其初始化 當線程2進入的時候雖然判定條件single==null 為false 而直接返回single,而此時的single 只是開闢了記憶體空間和賦值而並未被初始化 導致程式運行崩潰; 所以需要加上volatile。
3.通過構造一個靜態內部類實現單列模式的線程安全
public class Singleton5{ //私有化構造函數 private Singleton5(){ } //創建一個私有靜態內部類 並提供一個靜態方法 來創建單一實列對象 private static class Instance{ private static returnInstance(){ // 使用final關鍵字 讓single不可被修改 private static final Singleton5 single =new Singleton5();
} } //提供一個公共方法給外部來獲取唯一的對象 public static Singleton5(){ retrun Instance.returnInstance(); } }
這種方法通過靜態內部類來實現,由於在類載入的時候 內部類如果沒有被使用 其中的single仍然是不會被創建的,只有當外部調用getInstance方法時,它才會被載入從而創建single對象,所以這種方法來實現單列模式的線程安全即保證了在創建該類的實列時只能創建一個,又保證了線程的安全性,而且不會造成資源浪費。
總結
懶漢式和餓漢式的區別
從名字上來看,餓漢式急於創建類的唯一實列,即無論類是否調用,它都會創建好唯一的實列在那,雖然消耗一定的記憶體空間,但是其調用速度較快,並且實現代碼簡單。
相對於懶漢式 ,它是先不創建唯一實列,只有在調用getInstance方法時才會創建,所以叫懶漢式。 它基礎的實現方式只能適合單一線程使用,其餘的三種方式可以保證多線程的使用安全,而這三個方法又各有所長。
對於懶漢式的方法1來說,使用synchronized加鎖過大,導致其效率降低影響性能。對於方法2來說使用了雙層鎖定來實現,比方1的效率高性能要更好,但是其需要加上volatile關鍵字保證最初進入的兩個線程返回的結果一致,而對於volatile關鍵字涉及到了jvm的底層實現,我本身並不熟悉,所以有待以後加深瞭解,而且在其他單列模式實現的文章中看到過使用雙層鎖定的時候通過定義一個臨時變數來 替換第二層鎖,可以提高25%的效率,其具體的實現和原理也不太瞭解,有待以後學習。
從以上方法可看到,實現單列模式有以下要素
1.必須將構造方法私有化,防止外部創建對象
2.唯一的實列對象只能通過內部來創建。
3.必須要提供一個public方法讓外部能夠獲得這個唯一的實列對象。