大話設計模式筆記(十八)の單例模式

單例模式

定義

保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。編程

一般咱們可讓一個全局變量使得一個對象被訪問,但它不能防止你實例化多個對象。一個最好的辦法就是,讓類自身負責保存它的惟一實例。這個類能夠保證沒有其餘實例能夠建立,而且它能夠提供一個訪問該實例的方法。設計模式

UML圖

方式一:單線程下的單例

/**
 * Created by callmeDevil on 2019/8/17.
 */
public class Singleton {

    private static Singleton instance;

    private Singleton(){} //構造方法私有,防止外界建立實例

    // 得到本類實例的惟一全局訪問點
    public static Singleton getInstance(){
        if (instance == null) {
            //若實例不存在,則建立一個新實例,不然直接返回已有實例
            instance = new Singleton();
        }
        return instance;
    }

}

測試

public class Test {
    public static void main(String[] args) {
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        if (s1 == s2) {
            System.out.println("兩個對象是相同的實例");
        }
    }
}

測試結果

兩個對象是相同的實例

在沒有併發問題的狀況下,這種方式也是使用比較多的。但缺點也很明顯,多線程下根本無法用。服務器

方式二:多線程下的單例

/**
 * Created by callmeDevil on 2019/8/17.
 */
public class SingletonOnLock {

    private static SingletonOnLock instance;
    
    private SingletonOnLock(){}

    public static SingletonOnLock getInstance(){
        // 同步代碼塊,只有一個線程能進入,其餘阻塞
        synchronized (SingletonOnLock.class){
            if(instance == null){
                instance = new SingletonOnLock();
            }
        }
        return instance;
    }

}

存在問題

當存在對象實例時,徹底不用擔憂併發時致使中建立多個實例,但每次調用 getInstance() 方法時都被加鎖,是會影響性能的,所以這個類能夠繼續改良。多線程

方式三:雙重鎖定(DCL)

/**
 * Created by callmeDevil on 2019/8/17.
 */
public class SingletonOnDoubleCheckLock {

    private static SingletonOnDoubleCheckLock instance;

    private SingletonOnDoubleCheckLock(){}

    public static SingletonOnDoubleCheckLock getInstance(){
        // 先判斷實例是否存在,不存在再考慮併發問題
        if (instance == null) {
            synchronized (SingletonOnDoubleCheckLock.class){
                if(instance == null){
                    instance = new SingletonOnDoubleCheckLock();
                }
            }
        }
        return instance;
    }
    
}

兩次判斷實例是否存在的緣由

當實例存在時,就直接返回,這是沒有問題的。當實例爲空而且有兩個線程調用 getInstance() 方法時,它們均可以經過第一重 instace == null 的判斷,而後因爲 synchronized 機制,只有一個線程能夠進入,另外一個阻塞,必需要在同步代碼塊中的線程出來後,另外一個線程纔會進入。而此時若是沒有第二重的判斷,那第二個線程仍然會建立實例,這就達不到單例的目的了。併發

但這種方式是最讓人「詬病」的一種不推薦方式,技巧看上去很好,但實際上一樣影響性能性能

方式四:靜態初始化

/**
 * 該類聲明爲final ,阻止派生,由於派生可能會增長實例
 * Created by callmeDevil on 2019/8/17.
 */
public final class SingletonStatic {
    
    // 第一次引用類的任何成員時就建立好實例,同時沒有併發問題
    private static final SingletonStatic instance = new SingletonStatic();
    
    private SingletonStatic(){}
    
    public static SingletonStatic getInstance(){
        return instance;
    }
    
}

JVM第一次加載類的時候就已經建立好了實例,若是接下來的很長時間都沒有用到的話,佔用的內存至關於被浪費了,也不是最讓人推薦的一種方式。固然如今的服務器容量也愈來愈大,單單一個實例的內存也並非任何狀況都要考慮節省。除非追求極致。。測試

總結

  • 靜態初始化的方式是本身被加載時就已經將本身實例化,所以也被稱爲「餓漢式」。
  • 其餘方式是要在第一次被引用時,纔會將本身實例化,因此被稱爲「懶漢式」。
  • 其實單例模式還有不少種實現方式,下面再提一種《大話設計模式》中未提到的實現:靜態內部類。 這是樓主在《Java併發編程實戰》中看到的,也是最爲推薦的一種。

擴展

方式五:靜態內部類

/**
 * Created by callmeDevil on 2019/8/17.
 */
public class SingletonStaticClass {

    private SingletonStaticClass() {}

    public SingletonStaticClass getInstande() {
        return InterClass.instance;
    }

    // 靜態內部類,沒有併發問題
    private static final class InterClass {
        public static SingletonStaticClass instance = new SingletonStaticClass();
    }

}

推薦緣由

JVM第一次加載外部的 SingletonStaticClass 時,並不會直接實例化,因此這種方式也屬於「懶漢式」。只有在第一次調用 getInstance() 方法時,JVM纔會加載內部類 InterClass,接着才實例化靜態變量,也就是咱們須要的外部類的單例。這樣不只延時了實例化,同時也解決了併發訪問的問題,所以該方式是最爲推薦的一種方式。線程

相關文章
相關標籤/搜索