07.02 十分鐘瞭解設計模式:單例模式

十分鐘瞭解設計模式:單例模式

設計模式 Design Pattern

設計模式是一套反覆被人使用、多數人知曉的、經過分類編排的、對代碼設計經驗的總結。

使用設計模式可以使代碼可重用,讓代碼更加容易被他人理解,保證代碼可靠性。

基本的設計模式有23種:

如:單例模式、抽象工廠模式、建造者模式、工廠模式、原型模式、適配器模式、代理模式等等。

單例模式 Singleton

什麼是單例模式

單例模式是創建型模式類型中的一種,它確保一個類只有一個實例,而且自行實例化後,並向整個系統提供這個實例。主要包括線程安全的餓漢模式非線程安全的懶漢模式。

由定義可知,單例模式就是保證對象有且只有一個,防止出現一些不必要的問題,形象化的類比為一個公司只能有一個CEO,不然就會亂套了。

如果在開發中如配置文件、工具類、線程池、緩存和日誌系統等,如果創造出多個實例,就會導致許多問題,如佔用過多的資源,出現不一致的結果等。

為什麼要使用單例模式

單例模式的優點:

  • 使用單例模式,在內存中只有一個對象,可以節省內存空間
  • 避免頻繁的創建銷燬對象,提高程序的性能
  • 避免對共享資源的多重佔用
  • 可以實現工程內的全局訪問

基於上面的優點,在開發過程中如果遇到實例化頻率高、耗時過多、有狀態的工具類對象,就可以合理的去使用單例模式去設計代碼。

但是也要注意:

  • 多線程環境下,注意線程安全問題
  • 只能使用單例類提供的方法得到單例對象,不要使用反射,否則將會實例化一個新對象。
  • 不要做斷開單例類對象與類中靜態引用的危險操作。

三個要素

  • 私有的構造方法
  • 指向自己實例的私有靜態引用
  • 以自己實例為返回值的靜態的公有的方法

兩種模式

單例模式根據實例化對象時機的不同分為兩種:一種是餓漢式單例,一種是懶漢式單例。

package study.design.singleton;
/**
* 單例模式Singleton:
* 應用場合:有些對象只需要一個就可以了,如國家主席

* 作用:保證整個應用程序中某個實例有且只有一個
* 類型:餓漢模式和懶漢模式
* 區別:餓漢模式加載類時比較慢,但運行時獲取對象的速度比較快,是線程安全的。
* 懶漢模式類加載比較快,但運行時獲取對象的速度比較慢,不是線程安全的。
*/
public class Singleton {
public static void main(String[] args) {
//餓漢模式
EagerSingleton eagerSingleton1 = EagerSingleton.getInstance();
EagerSingleton eagerSingleton2 = EagerSingleton.getInstance();
if (eagerSingleton1 == eagerSingleton2) {
System.out.println("EagerSingleton實例一樣");
} else {
System.out.println("EagerSingleton實例不一樣");
}
//懶漢模式
LazySingleton lazySingleton1 = LazySingleton.getInstance();
LazySingleton lazySingleton2 = LazySingleton.getInstance();
if (lazySingleton1 == lazySingleton2) {
System.out.println("LazySingleton實例一樣");
} else {
System.out.println("LazySingleton實例不一樣");
}
}
}

線程安全的餓漢模式

餓漢模式是在單例的類被加載的時候,就直接實例化一個對象給自己的引用。

package study.design.singleton;
/**
* 餓漢模式:創建類的唯一實例,在類加載的時候就會創建這個類的實例,
* 就像餓了的人,迫不及待的想先吃到東西。
*/
public class EagerSingleton {
// 1. 將構造方法私有化,不允許外部直接創建對象
private EagerSingleton () {
}
// 2.創建類的唯一實例,在類加載的時候就會創建這個類的實例
private static EagerSingleton instance = new EagerSingleton();
// 3. 提供一個用於獲取實例的方法
public static EagerSingleton getInstance() {
return instance;
}
}

非線程安全的懶漢模式

懶漢模式是在取得實例方法的時候,先判斷實例化對象是否為null,如果不為null就實例化。

package com.study.pattern;
/**
* 懶漢模式:先判斷實例化對象是否為null,如果不為null就實例化
*/
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {
}

//synchronized對靜態工廠方法進行了同步處理,為了防止多線程環境中產生多個實例
//同步處理的恰當與否也是至關重要的。不然可能
會達不到得到單個對象的效果,還可能引發死鎖等錯誤
public static synchronized LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}

單例模式的陷阱

有關線程安全

  • 餓漢模式:
  • 餓漢模式下,因為對象類一加載就實例化,提前佔用系統資源,在getInstance()方法中只是直接返回對象引用,是線程安全的。
  • 缺點:如果在一個大環境下使用了過多的餓漢單例,則會生產出過多的實例對象,無論你是否要使用他們,Jvm垃圾回收器有可能不會收集這些單例對象。

Jvm卸載類的條件:

  • 該類所有的實例都已經被回收,也就是java堆中不存在該類的任何實例。
  • 加載該類的ClassLoader已經被回收。
  • 該類對應的java.lang.Class對象沒有任何地方被引用,無法在任何地方通過反射訪問該類的方法。

只有三個條件都滿足,jvm才會在垃圾收集的時候卸載類。顯然,單例的類不滿足條件一,因此單例類也不會被卸載。

  • 懶漢模式:
  • 因為懶漢模式是在需要的時候先進行判斷對象引用是否為NULL,如果多個線程同時進入判斷,就會生成多個實例對象。所以不是線程安全的,需要雙重鎖定來解決可能的線程安全問題。

線程安全的懶漢模式:

package com.study.pattern;
/**
* 懶漢模式:先判斷實例化對象是否為null,如果不為null就實例化
* 雙重鎖模式,提高效率
*/
public class LazySingleton {

private static LazySingleton instance;
private LazySingleton() {
}
//對靜態工廠方法進行了同步處理,為了防止多線程環境中產生多個實例
public static LazySingleton getInstance() {
if (instance == null) {
synchronized (LazySingleton.class) {
if (instance == null) {
instance = new LazySingleton();
}
}
}
return instance;
}
}

有關分佈式系統

當系統中的單例類被拷貝運行在多個虛擬機下的時候,在每一個虛擬機下都可以創建一個實例對象。

所以在使用分佈技術的系統中,應該避免使用單例模式,因為一個有狀態的單例類,在不同虛擬機上,各個單例對象保存的狀態很可能是不一樣的,問題也就隨之產生。

單例模式的序列化

正常的對象類,實現了 Serializable接口, 我們就可以把它往內存地寫再從內存裡讀出而"組裝"成一個跟原來一模一樣的對象。

不過當序列化遇到單例時,這裡邊就有了個問題: 從內存讀出而組裝的對象破壞了單例的規則。單例是要求一個Jvm中只有一個類對象的, 而現在通過反序列化,一個新的對象克隆了出來。

所以要使單例對象類可以進行序列化操作,可以加入readResolve方法。

package com.study.pattern;
import java.io.Serializable;
/**
* 懶漢模式:先判斷實例化對象是否為null,如果不為null就實例化
* 雙重鎖模式,提高效率
*/
public class LazySingleton implements Serializable {
private static final long serialVersionUID = -9062334136430456131L;
private static LazySingleton instance;
private LazySingleton() {
}
//對靜態工廠方法進行了同步處理,為了防止多線程環境中產生多個實例
public static LazySingleton getInstance() {
if (instance == null) {
synchronized (LazySingleton.class) {
if (instance == null) {
instance = new LazySingleton();
}
}
}
return instance;
}
/**
* 這樣當JVM從內存中反序列化地"組裝"一個新對象時,
* 就會自動調用這個readResolve方法來返回我們指定好的對象了, 單例規則也就得到了保證.

* readResolve()方法的返回值將會代替原來反序列化的對象.
* */
private Object readResolve() {
return instance;
}
}


分享到:


相關文章: