一、什么是單例模式
單例模式的定義就是確保某一個類只有一個實例,並且提供一個全局訪問點。屬於設計模式三大類中的創建型模式
。單例模式具有典型的三個特點
- 單例類只有一個實例對象;
- 該單例對象必須由單例類自行創建;
- 單例類對外提供一個訪問該單例的全局訪問點;
類圖如下:
單例模式優缺點
- 優點:由於單例模式只生成了一個實例,所以能夠節約系統資源,減少性能開銷,提高系統效率,同時也能夠嚴格控制客戶對它的訪問。
- 缺點:也正是因為系統中只有一個實例,這樣就導致了單例類的職責過重,違背了“單一職責原則”,同時也沒有抽象類,這樣擴展起來有一定的困難。
二、如何實現單例模式
常見的單例模式實現方式有五種:餓漢式
、懶漢式
、雙重檢測鎖式
、靜態內部類式
和枚舉單例
。而在這五種方式中餓漢式
和懶漢式
又最為常見。
1、餓漢式
餓漢式寫法是線程安全的,調用效率高。但是不能延時加載。代碼如下:
public class SingletonDemo1 { //線程安全的 //類初始化時,立即加載這個對象 private static SingletonDemo1 instance = new SingletonDemo1(); private SingletonDemo1() { } //方法沒有加同步塊,所以它效率高 public static SingletonDemo1 getInstance() { return instance; } }
由於該模式在加載類的時候對象就已經創建了,所以加載類的速度比較慢,但是獲取對象的速度比較快,且是線程安全的。
2、懶漢式
懶漢式寫法線程不安全。示例代碼:
public class SingletonDemo2 { //線程不安全的 private static SingletonDemo2 instance = null; private SingletonDemo2() { } //運行時加載對象 public static SingletonDemo2 getInstance() { if (instance == null) { instance = new SingletonDemo2(); } return instance; } }
由於該模式是在運行時加載對象的,所以加載類比較快,但是對象的獲取速度相對較慢,且線程不安全。如果想要線程安全的話可以加上synchronized
關鍵字,但是這樣會付出慘重的效率代價。
3、雙重檢測鎖
public class SingletonDemo3 { private static volatile SingletonDemo3 instance = null; private SingletonDemo3() { } //運行時加載對象 public static SingletonDemo3 getInstance() { if (instance == null) { synchronized(SingletonDemo3.class){ if(instance == null){ instance = new SingletonDemo3(); } } } return instance; } }
雙檢鎖,又叫雙重校驗鎖,綜合了懶漢式和餓漢式兩者的優缺點整合而成。看上面代碼實現中,特點是在synchronized關鍵字內外都加了一層 if 條件判斷,這樣既保證了線程安全,又比直接上鎖提高了執行效率,還節省了內存空間。
由於singleton=new Singleton()
對象的創建在JVM中可能會進行重排序,在多線程訪問下存在風險,使用volatile
修飾signleton
實例變量有效,解決該問題。
4、靜態內部類
public class Singleton { private Singleton(){ } public static Singleton getInstance(){ return Inner.instance; } private static class Inner { private static final Singleton instance = new Singleton(); } }
靜態內部類的方式效果類似雙檢鎖,但實現更簡單。但這種方式只適用於靜態域的情況,雙檢鎖方式可在實例域需要延遲初始化時使用。
5、枚舉單例
public enum Singleton { INSTANCE //doSomething 該實例支持的行為 //可以省略此方法,通過Singleton.INSTANCE進行操作 public static Singleton getInstance() { return Singleton.INSTANCE; } }
默認枚舉實例的創建是線程安全的,並且在任何情況下都是單例。實際上
- 枚舉類隱藏了私有的構造器。
- 枚舉類的域是相應類型的一個實例對象。
眾所周知,單例模式是創建型模式,都會新建一個實例。那么一個重要的問題就是反序列化。當實例被寫入到文件到反序列化成實例時,我們需要重寫readResolve
方法,以讓實例唯一。
private Object readResolve() throws ObjectStreamException{ return singleton; }
三、單例模式的擴展
單例模式可擴展為有限的多例(Multitcm)模式,這種模式可生成有限個實例並保存在 ArmyList 中,客戶需要時可隨機獲取,其結構圖如圖:
在日常場景中有,比如銀行櫃台有5個窗口,不管你隨機去哪個窗口都可以辦理業務,但是也就僅僅有5個業務員,實現代碼如下:
public class BusinessWindow { private static int maxBusinessWindow = 5; private static ArrayList<String> NoList = new ArrayList<String>(); private static ArrayList<BusinessWindow> businessWindowList = new ArrayList<BusinessWindow>(); private static int currentBusinessWindow = 0; static{ for(int i=0; i<maxBusinessWindow; i++){ businessWindowList.add(new businessWindowList(i+"號窗口")); } } private BusinessWindow(){} private BusinessWindow(String name){ NoList.add(name); } public static BusinessWindow getInstance(){ Random random = new Randow(); currentBusinessWindow = randow.nexInt(maxBusinessWindow); return businessWindowList.get(currentBusinessWindow); } public static void doSomething(){} }
四、常見應用場景
- 網站計數器。
- 項目中用於讀取配置文件的類。
- 數據庫連接池。因為數據庫連接池是一種數據庫資源。
- Spring中,每個
Bean
默認都是單例的,這樣便於Spring容器進行管理。 - Servlet中
Application
- Windows中任務管理器,回收站。
等等。