設計模式之單例模式詳解及代碼示例


一、什么是單例模式

  單例模式的定義就是確保某一個類只有一個實例,並且提供一個全局訪問點。屬於設計模式三大類中的創建型模式。單例模式具有典型的三個特點

  • 單例類只有一個實例對象;
  • 該單例對象必須由單例類自行創建;
  • 單例類對外提供一個訪問該單例的全局訪問點;

  類圖如下:

            

  單例模式優缺點

  • 優點:由於單例模式只生成了一個實例,所以能夠節約系統資源,減少性能開銷,提高系統效率,同時也能夠嚴格控制客戶對它的訪問。
  • 缺點:也正是因為系統中只有一個實例,這樣就導致了單例類的職責過重,違背了“單一職責原則”,同時也沒有抽象類,這樣擴展起來有一定的困難。

二、如何實現單例模式 

  常見的單例模式實現方式有五種:餓漢式懶漢式雙重檢測鎖式靜態內部類式枚舉單例。而在這五種方式中餓漢式懶漢式又最為常見。

  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();  
    }  
} 

  靜態內部類的方式效果類似雙檢鎖,但實現更簡單。但這種方式只適用於靜態域的情況,雙檢鎖方式可在實例域需要延遲初始化時使用。

  只有第一次調用getInstance方法時,虛擬機才加載 Inner 並初始化instance ,只有一個線程可以獲得對象的初始化鎖,其他線程無法進行初始化,保證對象的唯一性。目前此方式是所有單例模式中最推薦的模式,但具體還是根據項目選擇。

  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中任務管理器,回收站。
    等等。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM