摘要:
本文首先概述了單例模式產生動機,揭示了單例模式的本質和應用場景。緊接着,我們給出了單例模式在單線程環境下的兩種經典實現:餓漢式 和懶漢式,但是餓漢式是線程安全的,而懶漢式是非線程安全的。在多線程環境下使用雙重檢查模式。
類圖:
單例模式有 3 個特點:
- 單例類只有一個實例對象;
- 該單例對象必須由單例類自行創建;
- 單例類對外提供一個訪問該單例的全局訪問點;
使用場景:
1.數據庫連接池的連接類
2.工具類
3.配置的Bean中的方法缺省都爲餓漢式單例,在項目啓動的時候就已經生成了一個實例
4.配置文件數據的讀取,比如你的工程需要讀取配置文件,一般情況下你會寫個配置文件的類,而這個類在整個工程裏只需要new一次,所有調用者都是用同一個實例,那麼這個類就可以採用單例模式
併發情況下
當併發訪問的時候,第一個調用getInstance方法的線程A,在判斷完singleton是null的時候,線程A就進入了if塊準備創造實例,但是同時另外一個線程B在線程A還未創造出實例之前,就又進行了singleton是否爲null的判斷,這時singleton依然爲null,所以線程B也會進入if塊去創造實例,這時問題就出來了,有兩個線程都進入了if塊去創造實例,結果就造成單例模式並非單例。
餓漢式單例:
// 餓漢式單例
public class Singleton1 {
// 指向自己實例的私有靜態引用,主動創建
private static Singleton1 singleton1 = new Singleton1();
// 私有的構造方法
private Singleton1(){}
// 以自己實例爲返回值的靜態的公有方法,靜態工廠方法
public static Singleton1 getSingleton1(){
return singleton1;
}
}
餓漢式單例在單例類被加載時候,就實例化一個對象並交給自己的引用;而懶漢式單例只有在真正使用的時候纔會實例化一個對象並交給自己的引用。
懶漢式單例:
// 懶漢式單例
public class Singleton2 {
// 指向自己實例的私有靜態引用
private static Singleton2 singleton2;
// 私有的構造方法
private Singleton2(){}
// 以自己實例爲返回值的靜態的公有方法,靜態工廠方法
public static Singleton2 getSingleton2(){
// 被動創建,在真正需要使用時纔去創建
if (singleton2 == null) {
singleton2 = new Singleton2();
}
return singleton2;
}
}
從懶漢式單例可以看到,單例實例被延遲加載,即只有在真正使用的時候纔會實例化一個對象並交給自己的引用。
在多線程時候,使用單例模式進行雙重檢查鎖定:
// 線程安全的懶漢式單例
public class Singleton3 {
//使用volatile關鍵字防止重排序,因爲 new Instance()是一個非原子操作,可能創建一個不完整的實例
private static volatile Singleton3 singleton3;
private Singleton3() {
}
public static Singleton3 getSingleton3() {
// Double-Check idiom
if (singleton3 == null) { //第一次檢查是否創建
synchronized (Singleton3.class) { // 1
// 只需在第一次創建實例時才同步
if (singleton3 == null) { // 2 //第二次檢查
singleton3 = new Singleton3(); // 3
}
}
}
return singleton3;
}
}
當調用到單例模式時候,爲了在保證單例的前提下提高運行效率,我們需要對 singleton3 進行第二次檢查,目的是避開過多的同步(因爲這裏的同步只需在第一次創建實例時才同步,一旦創建成功,以後獲取實例時就不需要同步獲取鎖了)。這種做法無疑是優秀的,但是我們必須注意一點,必須使用volatile關鍵字修飾單例引用。
使用單例模式的好處:在一個應用程序中只能被實例化一次,可以很大程度的節省系統的開銷。