一、簡介:
我們經常有這樣的需求: 某一些類應該只存在一個實例 的時候,我們就可以用單例模式來應對.
單例模式:確保一個類只有一個實例,并提供一個全局訪問點.
單例模式是所有設計模式中最簡單的一個,也是大部分人最早知道的一個設計模式.
二、我們經常用的2種單例模式(懶漢式、餓漢式)
(1)餓漢式:
餓漢式單例類.在類初始化時,已經自行實例化
public class Singleton {
private Singleton() {}
private static Singleton singleton = new Singleton();
public static Singleton getInstance() {
return singleton ;
}
}
餓漢式在類創建的同時就已經創建好一個靜態的對象供系統使用,以后不再改變,所以是線程安全的。
(2)懶漢式
public class Singleton {
private static Singleton singleton;
private Singleton() {}
public synchronized static Singleton getInstance() {
if (null == singleton) {
singleton= new Singleton();
}
return singleton;
}
}
備注
單例模式的懶漢式實現方式體現了延遲加載的思想,什么是延遲加
載呢? 通俗點說,就是一開始不要加載
資源或者數據,一直等,等到馬上就要使用這個資源或者數據了,躲不過去了才加載,所以也稱
Lazy Load,不是懶惰啊,是“延遲加載”,這在實際開發中是一種很常見的思想,盡可能的節約資源。
單例模式的懶漢式實現還體現了緩存的思想,緩存也是實際開發中非常常見的功能。簡單講就是,如果
某些資源或者數據會被頻繁的使用,而這些資源或數據存儲在系統外部,比如數據庫、硬盤文件等,
那么每次操作這些數據的時候都從數據庫或者硬盤上去獲取,速度會很慢,會造成性能問題。 一個簡單的
解決方法就是:把這些數據緩存到內存里面,每次操作的時候,先到內存里面找,看有沒有這些數據,
如果有,那么就直接使用,如果沒有那么就獲取它,并設置到緩存中,下一次訪問的時候就可以直接從內存
中獲取了。從而節省大量的時間,當然,緩存是一種典型的空間換時間的方案。
兩種單例模式的比較
比較上面兩種寫法:
1、懶漢式是典型的時間換空間,也就是每次獲取實例都會進行判斷,看是否需要創建實例,費判斷的時間,當然,如果一直沒有人使用的話,那就不會創建實例,節約內存空間。
2、 餓漢式是典型的空間換時間,當類裝載的時候就會創建類實例,不管你用不用,先創建出來,然后每次調用的時候,就不需要再判斷了,節省了運行時間。
(3)雙重檢查加鎖
public class Singleton {
private volatile static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
//先檢查實例是否存在,如果不存在才進入下面的同步塊
if (instance == null) {
//同步塊,線程安全的創建實例
synchronized (Singleton.class) {
//再次檢查實例是否存在,如果不存在才真的創建實例
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
雙重檢查詳解
可以使用“雙重檢查加鎖”的方式來實現,就可以既實現線程安全,又能夠使性能不受到大的影響。
那么什么是“雙重檢查加鎖”機制呢? 所謂雙重檢查加鎖機制,指的是:并不是每次進入getInstance方法都需要同步,而是先不同步,進入方法過后,先檢查實例是否存在,如果不存在才進入下面的同步塊,這是第一重檢查。進入同步塊過后,再次檢查實例是否存在,如果不存在,就在同步的情況下創建一個實例,這是第二重檢查。這樣一來,就只需要同步一次了,從而減少了多次在同步情況下進行判斷所浪費的時間。 雙重檢查加鎖機制的實現會使用一個關鍵字volatile,它的意思是:被volatile修飾的變量的值,將不會被本地線程緩存,所有對該變量的讀寫都是直接操作共享內存,從而確保多個線程能正確的處理該變量。 這種實現方式既可使實現線程安全的創建實例,又不會對性能造成太大的影響,它只是在第一次創建實例的時候同步,以后就不需要同步了,從而加快運行速度。 注意:在Java1.4及以前版本中,很多JVM對于volatile關鍵字的實現有問題,會導致雙重檢查加鎖的失敗,因此雙重檢查加鎖的機制只能用在Java5及以上的版本。
(4)、靜態內部類單例模式
public class Singleton {
private static class Holder{
private static Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance(){
return Holder.INSTANCE;
}
}
當第一次加載Singleton類時并不會初始化INSTANCE,只有在第一次調用getInstance方法時才會導致INSTANCE被初始化。這種方式不僅能夠保證線程安全,也能保證單例對象的唯一性,同時也延長了單例的實例化。
小結
1、雙重檢查非常適用于高并發,我們熟知的開源庫Eventbus,ImageLoader等都是用的雙重檢查鎖方式實現單例
2、單例模式是運用頻率很高的模式,但是,由于在客戶端通常沒有高并發的情況,因此,選擇哪種實現方式都不會有太大的影響。即使如此,出于效率考慮,推薦使用DCL單例(雙重檢查鎖定)和靜態內部類單例模式。