1 單例模式簡介
1.1 定義
保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。
1.2 爲什麼要用單例模式呢?
簡單來說使用單例模式可以帶來下面幾個好處:
對於頻繁使用的對象,可以省略創建對象所花費的時間,這對於那些重量級對象而言,是非常可觀的一筆系統開銷;
由於 new 操作的次數減少,因而對系統內存的使用頻率也會降低,這將減輕 GC 壓力,縮短 GC 停頓時間;
比如說:線程池、緩存、對話框、註冊表、日誌對象、充當打印機、顯卡等設備驅動程序的對象。
3.爲什麼不使用全局變量確保一個類只有一個實例?
如果說這個對象非常消耗資源,而且程序某次的執行中一直沒用,這樣就造成了資源的浪費。利用單例模式的話,我們就可以實現在需要使用時才創建對象,這樣就避免了不必要的資源浪費。 不僅僅是因爲這個原因,在程序中我們要儘量避免全局變量的使用,大量使用全局變量給程序的調試、維護等帶來困難。
2 單例的模式的實現
通常單例模式在Java語言中,有兩種構建方式:
餓漢方式。指全局的單例實例在類裝載時構建
懶漢方式。指全局的單例實例在第一次被使用時構建。
不管是那種創建方式,它們通常都存在下面幾點相似處:
單例類必須要有一個 private 訪問級別的構造函數,只有這樣,才能確保單例不會在系統中的其他代碼內被實例化;
instance 成員變量和 uniqueInstance 方法必須是 static 的。
2.1 餓漢方式(線程安全)
public class Singleton {
//在靜態初始化器中創建單例,保證了線程安全
private static Singleton uniqueInstance= new Singleton();
private Singleton(){}
public static Singleton getInstance() {
return uniqueInstance;
}
}
所謂 “餓漢方式” 就是說JVM在加載這個類時就馬上創建此唯一的單例實例,不管你用不用,先創建了再說,如果一直沒有被使用,便浪費了空間,典型的空間換時間,每次調用的時候,就不需要再判斷,節省了運行時間。2.2 懶漢式(非線程安全和synchronized關鍵字線程安全版本 )
public class Singleton { private static Singleton uniqueInstance; private Singletion() { } public static Singleton getInstance() { //檢查實例,如果不存在,就進入同步代碼塊 if (uniqueInstance == null){ uniqueInstance = new Singletion(); } return uniqueInstance; } }
所謂 “餓漢方式” 就是說單例實例在第一次被使用時構建,而不是在JVM在加載這個類時就馬上創建此唯一的單例實例。
但是上面這種方式很明顯是線程不安全的,如果多個線程同時訪問getInstance()方法時就會出現問題。如果想要保證線程安全,一種比較常見的方式就是在getInstance() 方法前加上synchronized關鍵字,如下:
public static synchronized Singleton getInstance(){
if(uniqueSingleton == null){
uniqueSingleton = new Singleton();
}
return uniqueSingleton;
}
在程序中每次使用getInstance() 都要經過synchronized加鎖這一層,這難免會增加getInstance()的方法的時間消費,而且還可能會發生阻塞。我們下面介紹到的 雙重檢查加鎖版本 就是爲了解決這個問題而存在的。
2.3 懶漢式(雙重檢查加鎖版本)
利用雙重檢查加鎖(double-checked locking),首先檢查是否實例已經創建,如果尚未創建,“才”進行同步。這樣以來,只有一次同步,這正是我們想要的效果。
public class Singleton {
//volatile保證,當uniqueInstance變量被初始化成Singleton實例時,多個線程可以正確處理uniqueInstance變量
private volatile static Singleton uniqueInstance;
private Singletion() {
}
public static Singleton getInstance() {
//檢查實例,如果不存在,就進入同步代碼塊
if (uniqueInstance == null){
//只有第一次才徹底執行這裏的代碼
synchronized(Singleton.class) {
//進入同步代碼塊後,再檢查一次,如果仍是null,才創建實例
if(uniqueInstance == null){
uniqueInstance = new Singletion();
}
}
}
return uniqueInstance;
}
}
很明顯,這種方式相比於使用synchronized關鍵字的方法,可以大大減少getInstance() 的時間消費。
2.4 其他方式(枚舉)
除了上面說的幾種創建方式之外,還有挺多種其他的創建方式這裏稍微多提一點使用枚舉的方式,其他創建方式我們就不管了,沒有什麼實質性的作用。
枚舉實現單例的優點就是簡單,但是大部分應用開發很少用枚舉,可讀性並不是很高。個人感覺懶漢式(雙重檢查加鎖版本)還是使用挺多的,這種方式的可讀性也比較好。
public enum Singleton {
//定義一個枚舉的元素,它就是Singleton的一個實例
INSTANCE;
public void doSomeThing() {
System.out.println("枚舉方法實現單例");
}
}
使用方法:
public class ESTest{
public static void main(String[] args){
Singleton singleton = Singleton.INSTANCE;
singleton.doSomeThing();
}
}
2.5 總結
我們主要介紹到了以下幾種方式實現單例模式:
餓漢方式(線程安全)
懶漢式(非線程安全和synchronized關鍵字線程安全版本)
懶漢式(雙重檢查加鎖版本)
枚舉方式