今天開始學習設計模式,藉此機會學習並整理學習筆記。 設計模式是一門不區分語言的課程,什麼樣的編程語言都可以用到設計模式。如果說java語法規則比作武功招式的話,那麼設計模式就是心法。 設計模式共有23種,常見的19種,最常用的9-10種。 設計模式分三種類型:創建型、結構型、行為型; 其中創建型包含 ...
今天開始學習設計模式,藉此機會學習並整理學習筆記。
設計模式是一門不區分語言的課程,什麼樣的編程語言都可以用到設計模式。如果說java語法規則比作武功招式的話,那麼設計模式就是心法。
設計模式共有23種,常見的19種,最常用的9-10種。
設計模式分三種類型:創建型、結構型、行為型;
其中創建型包含單例設計模式、工廠模式、抽象工廠模式、原型模式、建造者模式;結構型包含代理模式、裝飾器模式、適配器模式、外觀模式、組合模式、享元模式、橋梁模式;行為型包含:策略模式、責任鏈模式、命令模式、中介者模式、模板方法模式、迭代器模式、訪問者模式、觀察者模式、解釋器模式、備忘錄模式、狀態模式。
好了,湊字完畢,開始今天的學習整理
Singleton設計模式,就是單例設計模式。
單例設計模式有句順口溜:單例一實例,私有構造器
單例設計模式分為餓漢式和懶漢式。
餓漢式是用戶沒有請求你的要求時,已經把這個實例提前創建出來了;
懶漢式則是需只有外部需要調用getInstance的時候,才回去初始化這個單例。
這樣就有一個區別:
餓漢式:用空間換時間
懶漢式:用時間換空間
下麵是具體代碼實現:
singleton 單例設計模式
1:餓漢式
餓漢式優缺點:
優點: 實現簡單,沒有多線程同步問題(預設條件下是線程安全的)
缺點:當類載入SingletonTest的時候,會初始化static的instance,靜態變數被創建並分配記憶體空間,從此,這個static的instance對象便一直占著此段記憶體(即使你並沒有使用該實例,當類被卸載時,靜態變數被摧毀,並釋放所占有的記憶體,因此在某些特定條件下會耗費記憶體。
簡單來說 用空間換時間
為什麼在餓漢式中不討論線程安全問題:
因為類裡面的靜態實例都是由類載入器來負責初始化的,類載入器classLoader在出現類名的時候就會開始工作,把類的靜態實例全部初始化,而且只初始化一次,所以一定是安全的,
public class Singleton {
//靜態一實例 將自身實例化對象設置為一個屬性,並用static final修飾
private static Singleton instance = new Singleton();
//私有構造器
private Singleton() {
}
//外部通過靜態方法返回該實例
public static Singleton getInstance() {
return instance;
}
}
2:懶漢式
懶漢式優缺點:
優點:實現起來比較簡單,當類SingletonTest被載入的時候,靜態變數static的instance未被創建並分配記憶體空間,當getInstance()方法第一次調用的時,初始化instance變數,並分配記憶體空間。在某些條件下這種方式會節省記憶體。
缺點:在多線程環境中這種方法是完全錯誤的,根本不能保證單例的狀態 需要添加synchronized(鎖)
public class Singleton2 {
//對象賦予null值 或者 不賦值
private static Singleton2 instance2;
//私有構造器
prviate Singleton2() {
}
//外部獲取實例對象 此時為了線程安全 需要使用synchronized(鎖)
public static synchronized Singleton2 getInstance2() {
if(instance2 == null) {
instance2 = new Singleton2();
}
return instance2;
}
}
3:如何兼顧餓漢式與懶漢式的優點:
第一種:使用靜態內部類的方法
優點:外部類載入時不予要立即載入內部類,內部類不被載入就不會初始化instance 故而占用記憶體。當Singleton 第一次載入時,並不需要去載入Singleton3handler只有當getInstance()方法第一次被 調用時,才會去初始化Instance,第一次調用getInstance()方法會導致虛擬機載入在SingletonHangdler類,這種方法不僅能確保線程安全,也能保證單例的唯一性,同時也延遲單例的實例化,
public class Singleton3 {
//構建靜態內部類
private static class Singleton3Handler{
private static Singleton3 instance3 = new Singleton3();
}
//私有構造器
private Singleton3() {
}
//外部獲取對象
public static Singleton3 getInstance() {
return Singleton3.Singleton3Handler.instance3;
}
}
第二種:使用枚舉方式
優點: 線程是安全的
public enum Singleton4{
INSTANCE;
public void method() {
//TODO
}
}
在任何情況下,它都是一個單例 我們可以直接 Singleton4.INSTANCE 引用
第三種:使用DCL模式 需要使用Volatile 關鍵字
雙重鎖懶漢式( Double Check Lock 簡稱 DCL )
優點:只有對象需要被使用的時候才創建,第一次判斷instance21 == null 為了避免非必要枷鎖,當第一次載入時才對實例進行枷鎖在實例化。這樣就可以節約記憶體空間,有可以保證線程安全。
缺點:但是,由於jvm存在亂序執行功能,DCL也會出現線程不安全的情況。
比如:
instance21 = new Singleton2;
這個步驟,其實在jvm裡面的執行分為三步:
1:在堆內開闢記憶體空間
2;在堆記憶體中實例化Singleton2裡面的各個參數
3: 把對象指向堆記憶體
但是這個缺點在jdk1.6以後 只需要定義 為: private volatile static Singleton5 instance5 = null; 就可以
解決此問題 。 volatile確保instance每次在均在主記憶體中讀取,這樣雖然會犧牲一點效率。
public class Singleton5{
// 對象一實例
private volatile static Singleton5 instance5;
//私有構造器
private Singleton5() {
}
//DCL懶漢式
public static Singleton5 getInstance() {
if(instance5==null) {
synchronized(Singleton5.class){
if(instance5 == null) {
instance5 = new Singleton5();
}
}
}
return instance5;
}
}
需要註意的是:
需要延遲載入的情況下使用第一、第二種;
需要防止序列化問題、反射攻擊使用第三種。
才疏學淺,如有錯誤,懇請指教!