1. 單例模式
什麼是單例模式?簡言之就是確保定義為單例模式的類在程式中有且只有一個例項。單例模式的特點:
- 只有一個例項 (只能有一個物件被建立)
- 自我例項化(類構造器私有)
- 對外提供獲取例項的靜態方法
2.單例模式的實現
常見的單例模式實現方式有五種:
2.1. 懶漢式
懶漢式(一般也稱之為 飽漢式),具體程式碼實現如下:
public class Singleton {
/**
* 自我例項化
*/
private static Singleton singleton;
/**
* 構造方法私有
*/
private Singleton() {
System.out.println("建立單例例項...");
}
/**
* 對外提供獲取例項的靜態方法
*/
public static Singleton getInstance() {
if (null == singleton) {
singleton = new Singleton();
}
return singleton;
}
}複製程式碼
從程式碼實現中可以看到,例項並不是在一開始就是初始化的,而是在呼叫 getInstance()方法後才會產生單例,這種模式延遲初始化例項,但它並非是執行緒安全的。
public class SingleTonTest {
/**
* 多執行緒模式下測試懶漢模式是否執行緒安全
*
* @param args
*/
public static void main(String[] args) {
/**
* 這裡我圖方便,直接用Executors建立執行緒池
* 阿里巴巴開發手冊是不推薦這麼做的
*/
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i < 20; i++) {
executorService.execute(() -> System.out.println(Thread.currentThread().getName() + "::" + Singleton.getInstance()));
}
}
}複製程式碼
測試結果截圖:
懶漢式是在執行時載入物件的,所以載入該單例類時會比較快,但是獲取物件會比較慢。且這樣做是執行緒不安全的,如果想要執行緒安全,可以在getInstance()方法加上synchronized 關鍵詞修飾,但這樣會讓我們付出慘重的效率代價。
2.2. 餓漢式
提前建立好例項物件,呼叫效率高,但無法延時載入,容易產生垃圾,執行緒安全。
public class Singleton {
/**
* 自我例項化
*/
private static Singleton singleton = new Singleton();
/**
* 構造方法私有
*/
private Singleton() {
System.out.println("建立單例例項...");
}
/**
* 對外提供獲取例項的靜態方法
*/
public static Singleton getInstance() {
return singleton;
}
}複製程式碼
2.3. 雙重檢查鎖模式
public class Singleton {
/**
* 自我例項化,volatile修飾,保證執行緒間可見
*/
private volatile static Singleton singleton;
/**
* 構造方法私有
*/
private Singleton() {
System.out.println("建立單例例項...");
}
/**
* 對外提供獲取例項的靜態方法
*/
public static Singleton getInstance() {
// 第一次檢查,避免不必要的例項
if (singleton == null) {
// 第二次檢查,同步,避免產生多執行緒的問題
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}複製程式碼
由於singleton=new Singleton()
物件的建立在JVM中可能會進行重排序,在多執行緒訪問下存在風險,使用volatile
修飾signleton
例項變數,能禁止指令重排,使得物件在多執行緒間可見,能夠有效解決該問題。
雙重檢查鎖定失敗的問題並不歸咎於 JVM 中的實現 bug,而是歸咎於 Java 平臺記憶體模型。記憶體模型允許所謂的“無序寫入”,這也是這些習語失敗的一個主要原因
2.4. 靜態內部類模式
public class Singleton {
/**
* 構造方法私有
*/
private Singleton() {
System.out.println("建立單例例項...");
}
private static class SingletonInner {
private static Singleton instance = new Singleton();
}
private static Singleton getInstance() {
return SingletonInner.singleton;
}
}複製程式碼
這樣寫充分利用靜態內部類的特點——初始化操作和外部類是分開的,只有首次呼叫getInstance()方法時,虛擬機器才載入內部類(SingletonInner.class)並初始化instance, 保證物件的唯一性。
2.5. 列舉單例模式
public enum Singleton {
INSTANCE
}複製程式碼
感覺異常簡單,預設列舉類建立的物件都是單例的,且支援多執行緒。
3.單例模式總結
- 單例模式優點在於:全域性只會生成單個例項,所以能夠節省系統資源,減少效能開銷。然而也正是因為只有單個例項,導致該單例類職責過重,違背了“單一職責原則”,單例類也沒有抽象方法,會導致比較難以擴充套件。
- 以上所有單例模式中,推薦使用靜態內部類的實現,非常直觀,且保證執行緒安全。在《Effective Java》中推薦列舉類,但太簡單了,導致程式碼的可讀性比較差。
- 單例模式是建立型模式,反序列化時需要重寫readResovle()方法,以保證例項唯一。
文章首發於本人部落格:www.developlee.top, 轉載請註明出處!
關注公眾號,後臺回覆666, 領取福利: