一、簡介
單例模式:不能自由構造對象、在應用中隻存在一個執行個體的情況。
例如:圖檔加載器中一般都含有這麼幾個部分:RequestManager(請求管理器)、線程池、Engine(資料擷取引擎)、MemoryCache、DiskLRUCache、Transformation(圖檔處理)、Target等子產品,很消耗資源,沒有必要也不應該建立多個執行個體。
寫法有多種,下面分析常用的懶漢單例模式的優缺點,并做出最優選擇。
二、常用單例模式分析
1. 傳統懶漢模式
代碼:
public class Singleton1 {
private static Singleton1 sInstance;
private Singleton1() {
}
public static synchronized Singleton1 getInstance() {
if (sInstance == null) {
sInstance = new Singleton1();
}
return sInstance;
}
}
* 優點:第一次調用getInstance()時才初始化,synchronized保證線程安全
* 缺點:每次調用getInstance()時都會同步,即使sInstance已經初始化過了,造成不必要同步開銷
* 結論:不推薦
2. DCL(Double Check Lock)
代碼:
public class Singleton2 {
private static Singleton2 sInstance;
private Singleton2() {
}
public static Singleton2 getInstance() {
if (sInstance == null) {
synchronized(Singleton2.class) {
if (sInstance == null) {
sInstance = new Singleton2();
}
}
}
return sInstance;
}
}
getInstance()方法進行了兩次判空,第一次是為了避免不必要的同步,第二次則是在null的情況下建立執行個體。
看起來很好但是存在問題:因為sInstance = new Singleton2()這句不是原子操作(可能會被打斷),它可分為3步:
- 給sInstance配置設定記憶體
- 調用構造函數初始化成員變量
- 将sInstance指向配置設定的記憶體空間(執行完本操作後,sInstance != null)
假設:線程A執行到sInstance = new Singleton2()這步,走的順序為1->3->2,當執行完3後,線程B直接取走了非空的sInstance(但并沒有初始化),使用時就會出錯,導緻DCL(Double Check Lock)失效
* 優點:懶加載,第一次執行getInstance()時單例對象才會執行個體化,資源使用率高、效率高;
* 缺點:高并發時有風險(小機率發生錯誤);第一次加載稍慢
* 結論:能滿足絕大部分使用場景,推薦使用
3.DCL改善
代碼:
public class Singleton3 {
private volatile static Singleton3 sInstance;
private Singleton3() {
}
public static Singleton3 getInstance() {
if (sInstance == null) {
synchronized (Singleton3.class) {
if (sInstance == null) {
sInstance = new Singleton3();
}
}
}
return sInstance;
}
}
在DCL基礎上,将sInstance設為volatile
volatile:1.保證線程本地不會有變量副本,每次都從主記憶體中讀取;2.保證變量的寫操作都先行發生在後面對于它的讀操作
* 優點:每次都從主記憶體中讀取sInstance,将DCL中sInstance = new Singleton2()操作原子化,避免錯誤
* 缺點:多少影響一點性能
* 結論:可以選擇
4. 靜态内部類單例
代碼:
public class Singleton4 {
private Singleton4() {
}
private static class Holder {
private static final Singleton4 INSTANCE = new Singleton4();
}
public static Singleton4 getInstance() {
return Holder.INSTANCE;
}
}
* 優點:懶加載、線程安全、保證單例對象的唯一性(靜态内部類隻會被加載一次)
* 缺點:無
* 結論:最佳選擇
5. 枚舉單例
代碼:
public enum Singleton5 {
INSTANCE
}
* 優點:寫法簡單
* 缺點:枚舉比較占資源,官方不是很推薦
* 結論:不是很推薦
三、結論
最佳選擇: 4. 靜态内部類單例
第二選擇:2. DCL
第三選擇:3. DCL改善
(個人看法)