說明:筆者採用JAVA語言對《劍指Offer(第2版)》的題目求解。
單例模式—JAVA版本
劍指Offer第2版 P32頁:面試題2:實現Singleton模式
我們先來看看什麼是單例模式?
單例模式的核心是保證一個類只有一個實例,並且提供一個訪問實例的全局訪問點。
單例模式有以下特點:
1、單例類只能有一個實例。
2、單例類必須自己創建自己的唯一實例。
3、單例類必須給所有其他對象提供這一實例。
單例模式確保某個類只有一個實例,而且自行實例化並向整個系統提供這個實例。在計算機系統中,線程池、緩存、日誌對象、對話框、打印機、顯卡的驅動程序對象常被設計成單例。這些應用都或多或少具有資源管理器的功能。每臺計算機可以有若干個打印機,但只能有一個Printer Spooler,以避免兩個打印作業同時輸出到打印機中。每臺計算機可以有若干通信端口,系統應當集中管理這些通信端口,以避免一個通信端口同時被兩個請求同時調用。
通常單例模式的實現有兩種模式:
- 懶漢式:指全局的單例實例在第一次被使用時構建。
- 餓漢式:指全局的單例實例在類裝載時構建。
Singleton模式實現—1 非線程安全的實現(簡單版本)
這可能是最簡單的實現方法了。每次獲取instance之前先進行判斷,如果instance爲空就new一個出來,否則就直接返回已存在的instance。這種寫法在大多數的時候也是沒問題的。
但是仔細閱讀代碼我們就會發現,這個寫法是非線程安全的寫法。換句話說,只適合於單線程應用。這是非常不好的寫法。
問題在於,當多線程工作的時候,如果有多個線程同時運行到 if (singleton == null),都判斷爲null,那麼兩個線程就各自會創建一個實例——這樣一來,就不是單例了。
package com.bean.singleton;
public class SingletonDemo_1 {
//聲明單例對象singleton
private static SingletonDemo_1 singleton = null;
//構造方法
private SingletonDemo_1() {
}
//獲得類對象的方法 getInstance()
public static SingletonDemo_1 getInstance() {
//基本思想是如果singleton對象爲null,那麼就new一個對象,並返回它。
if (singleton == null) {
singleton = new SingletonDemo_1();
}
return singleton;
}
}
Singleton模式實現—2 加線程鎖(synchronized)的版本
加上synchronized關鍵字之後,getInstance方法就會鎖上了。如果有兩個線程(T1、T2)同時執行到這個方法時,會有其中一個線程T1獲得同步鎖,得以繼續執行,而另一個線程T2則需要等待,當第T1執行完畢getInstance之後(完成了null判斷、對象創建、獲得返回值之後),T2線程纔會執行執行。——所以這段代碼也就避免了上一個簡單版本中可能出現因爲多線程導致多個實例的情況。
但是,這種寫法也有一個問題:給getInstance()方法加鎖,雖然會避免了可能會出現的多個實例問題,但是會強制除T1之外的所有線程等待,實際上會對程序的執行效率造成負面影響。
package com.bean.singleton;
public class SingletonDemo_2 {
/**
* 定義一個變量來存儲創建好的類實例
*/
private static SingletonDemo_2 singleton = null;
/**
* 私有化構造方法,好在內部控制創建實例的數目
*/
private SingletonDemo_2() {
}
/**
* 定義一個方法來爲客戶端提供類實例
*
* @return 一個Singleton的實例
*/
public static synchronized SingletonDemo_2 getInstance() {
// 判斷存儲實例的變量是否有值
if (singleton == null) {
// 如果沒有,就創建一個類實例,並把值賦值給存儲類實例的變量
singleton = new SingletonDemo_2();
}
// 如果有值,那就直接使用
return singleton;
}
/**
* 示意方法,單例可以有自己的操作
*/
public void singletonOperation() {
//功能處理
}
/**
* 示意屬性,單例可以有自己的屬性
*/
private String singletonData;
/**
* 示意方法,讓外部通過這些方法來訪問屬性的值
*
* @return 屬性的值
*/
public String getSingletonData() {
return singletonData;
}
}
Singleton模式實現—3 加了volatile關鍵字的double-check版本
注意其中有兩次if (instance == null)的判斷,這個叫做雙重檢查( Double-Check)。
這個版本代碼雖然比較簡單,但是實現的思想比較複雜,解釋起來更加複雜。涉及原則操作和中間狀態。
先不做解釋了,後續補充。
package com.bean.singleton;
public class SingletonDemo_3 {
/**
* 對保存實例的變量添加volatile的修飾
*/
private volatile static SingletonDemo_3 instance = null;
private SingletonDemo_3() {
}
public static SingletonDemo_3 getInstance() {
// 先檢查實例是否存在,如果不存在才進入下面的同步塊
if (instance == null) {
// 同步塊,線程安全的創建實例
synchronized (SingletonDemo_3.class) {
// 再次檢查實例是否存在,如果不存在才真的創建實例
if (instance == null) {
instance = new SingletonDemo_3();
}
}
}
return instance;
}
}
Singleton模式實現—4 餓漢模式
聲明瞭一個private static類型的實例,並且直接創建它。
注意構造方法是private類型的。
package com.bean.singleton;
public class SingletonDemo_4 {
/*
* 定義一個靜態變量來存儲創建好的類實例
* 直接在這裏創建類實例,只會創建一次
*/
private static SingletonDemo_4 instance = new SingletonDemo_4();
//私有化構造方法,好在內部控制創建實例的數目
private SingletonDemo_4(){
}
/*
* 定義一個類級別的方法來提供類實例,即該方法採用static修飾
* 這個方法裏面就不需要控制代碼了
*
*/
public static SingletonDemo_4 getInstance(){
//直接使用已經創建好的實例
return instance;
}
}
Singleton模式實現—5 Holder模式
該模式是將懶加載模式和線程安全模式完美結合的一種模式
package com.bean.singleton;
public class SingletonDemo_5 {
/**
* 類級的內部類,也就是靜態的成員式內部類,該內部類的實例與外部類的實例 沒有綁定關係,而且只有被調用到纔會裝載,從而實現了延遲加載
*/
private static class SingletonHolder {
/**
* 靜態初始化器,由JVM來保證線程安全
*/
private static SingletonDemo_5 instance = new SingletonDemo_5();
}
/**
* 私有化構造方法
*/
private SingletonDemo_5() {
}
public static SingletonDemo_5 getInstance() {
return SingletonHolder.instance;
}
}