23種設計模式入門 -- 單例模式

單例模式:採用必定的方法,使得軟件運行中,對於某個類只能存在一個實例對象,而且該類只能提供一個取得實例的方法。java

分類安全

  • 餓漢式
    1. 靜態常量方式
    2. 靜態代碼塊方式
  • 懶漢式
    1. 普通方式,線程不安全
    2. 同步方法方式,線程安全
    3. 同步代碼塊方式,線程不安全
  • 其餘方式
    1. 雙重檢查
    2. 靜態內部類
    3. 枚舉

實現思路:多線程

  1. 想要實現單例,即不能讓外部隨意的去實例化對象。因此須要構造器私有線程

  2. 既然不容許外部去建立了,因此須要在類的內部建立對象code

  3. 外部須要使用對象,因此須要對外提供一個獲取實例的方法對象

1、餓漢式

根據對象建立的時機,能夠分爲餓漢式和懶漢式。餓漢式,即在類加載的時候當即建立實例,根據所學知識咱們能夠很快想到要使用static關鍵字將屬性和類相關聯。如下提供兩種書寫方式供參考。進程

特色內存

  • 優勢:寫法簡單,在類裝載的時候就完成實例化,避免了線程同步問題
  • 缺點:在類裝載的時候就完成了實例化,若是開始至終都沒有使用這個實例,會形成內存浪費

1.靜態常量方式

class Singleton01{
    //構造器私有,防止外部new
    private Singleton01(){

    }
    //內部建立對象
    private final static Singleton01 instance = new Singleton01();

    //提供給外部建立實例的靜態方法
    public static Singleton01 getInstance(){
        return instance;
    }

}

2.靜態代碼塊方式

class Singleton02{
    //構造器私有,防止外部new
    private Singleton02(){

    }
    //內部建立對象
    private static Singleton02 instance;
    static {
        instance = new Singleton02();
    }

    //提供給外部建立實例的靜態方法
    public static Singleton02 getInstance(){
        return instance;
    }

}

這種方式和靜態常量的實現方式邏輯和優缺點是同樣的,只是寫法不一樣。資源

2、懶漢式

懶漢式,即在類加載時並不實例化對象,等到使用對象實例的時候纔去實例化,也被稱爲懶加載效果。這樣作的好處能夠節約資源,減小浪費,只有須要的時候採起建立,不須要就不會實例化。get

1.普通方式(線程不安全)

class Singleton01{
    // 構造器私有
    private Singleton01(){

    }
    // 定義靜態變量,實例化留在獲取實例的getInstance方法,起到懶加載效果
    private static Singleton01 instance;

    public static Singleton01 getInstance(){
        // 判斷若是爲空才建立,起到懶加載
        if (instance == null){
            instance = new Singleton01();
        }

        return instance;
    }
}

問題:在多線程狀況下,假設類還未第一次實例化,此時兩個進程同時執行到了if(instance==null),而將來得及往下執行時,此時二者校驗都成立,都會執行實例化操做,將有可能出現建立多個實例的問題。

2.同步方法方式(線程安全)

既然存在線程安全問題,確定會想到使用synchronized關鍵字來解決

class Singleton02{
    private static Singleton02 instance;

    private Singleton02(){

    }
    //使用synchronized關鍵字來實現線程安全
    public static synchronized Singleton02 getInstance(){
        if (instance == null){
            instance = new Singleton02();
        }
        return instance;
    }
}

這樣的方式能夠起到線程安全的效果,可是每一個線程都須要等待鎖,因此又會存在效率低的問題,因而有人想到了將鎖的範圍縮小到方法的內部,使用同步代碼塊的方式

3.同步代碼塊方式(線程不安全)

這樣的方式好很差呢?先看代碼

class Singleton03{
    private static Singleton03 instance;

    private Singleton03(){

    }

    public static Singleton03 getInstance(){
        if (instance == null){
            // 這裏的synchronized其實沒有實際意義,可能會產生多個實例
            synchronized (Singleton03.class){ 
                instance = new Singleton03();
            }
        }
        return instance;
    }
}

這樣鎖的範圍是變小了,可是還會存在多個線程同時判斷到if (instance == null),即便在後面加上鎖,依舊會在後續建立實例,只是延遲了一點而已,因此這種寫法不可取

3、其餘方式

1.雙重檢查

爲了可以實現懶加載的效果,同時兼顧效率,因而出現了這種寫法

class Singleton01{
    //volatile,當有發生變化時即時儲存到內存中。防止指令重排
    private static volatile Singleton01 instance;

    private Singleton01(){

    }

    //雙重檢查,解決線程同步問題,又保證效率
    public static Singleton01 getInstance(){
        if (instance == null){ // 第一次檢查,下降產生鎖的機率
            synchronized (Singleton01.class){
                if(instance == null){ // 第二次檢查,保證線程安全
                    instance = new Singleton01();
                }
            }
        }
        return instance;
    }
}

使用雙重檢查,第一次檢查提高效率,第二次檢查保證線程安全,簡直美滋滋

2.靜態內部類

利用靜態內部類在被調用時纔會加載,即存在懶加載效果,因此也能夠這樣寫

class Singleton02{
    private Singleton02(){

    }

    /*
        靜態內部類在外部類裝載的時候不會立刻執行,起到懶加載做用。
        類的靜態屬性只有在第一次使用的時候纔會加載,JVM在類加載時是線程安全的
     */
    private static class SingletonInstance{
        private static final Singleton02 INSTANCE = new Singleton02();
    }

    public static Singleton02 getInstance(){
        return SingletonInstance.INSTANCE;
    }

}

3.枚舉

枚舉方式是最簡單的寫法,也是被不少人推崇的寫法

enum Singleton03{
    INSTANCE;
}

簡單明瞭...

4、小結

使用單例模式,可使一個類只存在一個實例對象,從而節省了系統資源。

上文中列出了8個寫法,其中懶加載的寫法存在線程安全和效率的問題,須要謹慎使用。比較推薦的寫法有5種:懶加載2種+其餘方式3種。當認定單例的對象在軟件中必定會用到,可使用懶加載,反之可使用其餘方式

相關文章
相關標籤/搜索