JAVA中常用的幾種設計模式--單例
前段時間面試的時候被問到了設計模式,結果想想只瞭解單例、工廠…囧,所以整理下,溫故而知新。
設計模式:簡單說就是前人留下的一些經驗,有助於提高程式碼的複用率,增加可讀性;
單例模式應該是使用比較多的模式之一,很多人都是一知半解,其中也包括我,哈哈
單例模式
定義:確保一個類只有一個例項,並且提供一個全域性訪問點;
優點:該類的例項只有一個;全域性共享;縮短物件的建立時間;
七種寫法
單例模式的各種寫法其實有利有弊,簡單分析下
餓漢模式
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){
}
public static Singleton getInstance() {
return instance;
}
}
在類載入的時候就建立,以空間換時間的做法。
java中的runtime 類就是採用的餓漢模式
public class Runtime {
private static Runtime currentRuntime = new Runtime();
/**
* Returns the runtime object associated with the current Java application.
* Most of the methods of class <code>Runtime</code> are instance
* methods and must be invoked with respect to the current runtime object.
*
* @return the <code>Runtime</code> object associated with the current
* Java application.
*/
public static Runtime getRuntime() {
return currentRuntime;
}
/** Don't let anyone else instantiate this class */
private Runtime() {}
//以下程式碼省略
}
總結:這種方式簡單粗暴,對於佔用記憶體少,載入時間短的部分單例可以使用這種方式。但是對於啟動時間有要求,佔用記憶體資源較多的類 不建議這麼寫。
懶漢模式
非執行緒安全
public class Singleton { private static Singleton instance; private Singleton (){ } public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
懶漢模式申明瞭一個靜態物件,在第一次載入的時候會例項化。延時載入了,但是是非執行緒安全的,可能建立多個物件例項。
- 執行緒安全
public class Singleton {
private static Singleton instance;
private Singleton (){
}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
因為這種方式在getInstance()方法上加了同步鎖,所以在多執行緒情況下會造成執行緒阻塞,把大量的執行緒鎖在外面,只有一個執行緒執行完畢才會執行下一個執行緒。因為鎖住getInstance 導致執行的效率比較的低;
- 執行緒安全,雙重校驗鎖
public class Singleton {
/**
* 注意此處使用的關鍵字 volatile,
* 被volatile修飾的變數的值,將不會被本地執行緒快取,
* 所有對該變數的讀寫都是直接操作共享記憶體,從而確保多個執行緒能正確的處理該變數。
*/
private volatile static Singleton singleton;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized(Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return singleton;
}
}
這種寫法在getSingleton()方法中對singleton進行了兩次判空,第一次是為了不必要的同步,第二次是在singleton等於null的情況下才建立例項。
雙重校驗鎖:既可以達到執行緒安全,也可以使效能不受很大的影響,換句話說在保證執行緒安全的前提下,既節省空間也節省了時間,集合了「餓漢式」和兩種「懶漢式」的優點,取其精華,去其槽粕。
需要說明的是: 對於volatile關鍵字,還是存在很多爭議的。由於volatile關鍵字可能會遮蔽掉虛擬機器中一些必要的程式碼優化,所以執行效率並不是很高。也就是說,雖然可以使用“雙重檢查加鎖”機制來實現執行緒安全的單例,但並不建議大量採用,可以根據情況來選用。
還有就是在java1.4及以前版本中,很多JVM對於volatile關鍵字的實現的問題,會導致“雙重檢查加鎖”的失敗,因此“雙重檢查加鎖”機制只只能用在java1.5及以上的版本。
- 靜態內部類
很多時候,java都給我們提供了同步的控制;
比如:
static{…}區塊初始化資料的時候。
final變數被訪問的時候。
因為在JVM進行類載入的時候他會保證資料是同步的,我們可以這樣實現:採用內部類,在這個內部類裡面去建立物件例項。這樣的話,只要應用中不使用內部類 JVM 就不會去載入這個單例類,也就不會建立單例物件,從而實現「懶漢式」的延遲載入和執行緒安全。
public class Singleton {
private Singleton(){
}
public static Singleton getInstance(){
return SingletonHolder.sInstance;
}
private static class SingletonHolder {
private static final Singleton sInstance = new Singleton();
}
}
- 列舉
《Java與模式》中,作者這樣寫道,使用列舉來實現單例項控制會更加簡潔,而且無償地提供了序列化機制,並由JVM從根本上提供保障,絕對防止多次例項化,是更簡潔、高效、安全的實現單例的方式。
public enum Singleton {
//定義一個列舉的元素,它就是 Singleton 的一個例項
INSTANCE;
public void doSomeThing() {
// do something...
}
}
使用方式:
public static void main(String args[]) {
Singleton singleton = Singleton.instance;
singleton.doSomeThing();
}
實現簡單,但是可讀性比較的差
- 使用容器
public class SingletonManager {
private static Map<String, Object> objMap = new HashMap<String,Object>();
private Singleton() {
}
public static void registerService(String key, Objectinstance) {
if (!objMap.containsKey(key) ) {
objMap.put(key, instance) ;
}
}
public static ObjectgetService(String key) {
return objMap.get(key) ;
}
}
這種事用SingletonManager 將多種單例類統一管理,在使用時根據key獲取物件對應型別的物件。這種方式使得我們可以管理多種型別的單例,並且在使用時可以通過統一的介面進行獲取操作,降低了使用者的使用成本,也對使用者隱藏了具體實現,降低了耦合度。
總結:
對於以上七種單例,分別是「餓漢式」、「懶漢式(非執行緒安全)」、「懶漢式(執行緒安全)」、「雙重校驗鎖」、「靜態內部類」、「列舉」和「容器類管理」。很多時候取決人個人的喜好,雖然雙重檢查有一定的弊端和問題,但我就是鍾愛雙重檢查,覺得這種方式可讀性高、安全、優雅(個人觀點)。