單例模式的九種實現方式

單例模式

單例簡介

因進程需要,有時我們只需要某個類同時保留一個對象,不希望有更多對象。
單例模式的特點:
1.單例模式只能有一個實例。
2.單例類必須創建自己的唯一實例。
3.單例類必須向其他對象提供這一實例。

懶漢式

特點:
線程不安全,如果多線程同時訪問,會創造出多個對象。

/**
 * 通過提供一個靜態的對象 SINGLE_TON_DEMO_1
 * 利用private修飾的構造方法和getInstance()提供一個單例
 *
 * 缺點:線程不安全,如果多個線程同時訪問,會構造出多個對象
 */
public class SingleTonDemo1 {
    private static  SingleTonDemo1 SINGLE_TON_DEMO_1 ;
    private SingleTonDemo1(){}

    public static SingleTonDemo1 getInstance(){
        if (SINGLE_TON_DEMO_1==null) {
            SINGLE_TON_DEMO_1 = new SingleTonDemo1();
        }
        return SINGLE_TON_DEMO_1;
    }

}

實例化方法加synchronized的懶漢式

特點:
線程安全,但是效率不高。
由於併發並不是隨時都在發生,大多數情況下這個鎖佔用的資源都浪費了。

/**
 * 併發不是隨時都在發生,大多數時候這個鎖佔用的資源都浪費了
 * 雖然線程安全,但是效率不高
 */
public class SingleTonDemo2 {
    private static SingleTonDemo2 singleTonDemo2;
    private SingleTonDemo2(){}

    public static synchronized SingleTonDemo2 getInstance(){
        if (singleTonDemo2 == null) {
            singleTonDemo2 = new SingleTonDemo2();
        }
        return singleTonDemo2;
    }
}

餓漢式

特點:
線程安全

/**
 *  線程安全
 *  相比於靜態方類,內存常駐
 */
public class SIngleTonDemo3 {

    private static SIngleTonDemo3 sIngleTonDemo3 = new SIngleTonDemo3();
    private SIngleTonDemo3(){}

    public static SIngleTonDemo3 getInstance(){
        return sIngleTonDemo3;
    }
}

靜態內部類加載

特點:
1.線程安全
2.使用靜態內部類的好處是,不會隨着類的加載而加載,而是在調用getInstance()方法時再加載,達到類似懶漢模式的效果,而這種方式是線程安全的

/**
 * 線程安全
 *
 * 使用靜態內部類的好處是,不會隨着類的加載而加載,而是在調用getInstance()方法時
 * 再加載,達到類似懶漢模式的效果,而這種方式是線程安全的
 */
public class SingleTonDemo4 {
    public static class SingleTonHolder{
        private static SingleTonDemo4 singleTonDemo4 = new SingleTonDemo4();
    }

    private SingleTonDemo4(){}

    public static SingleTonDemo4 getInstance(){
        return SingleTonHolder.singleTonDemo4;
    }


}

枚舉方法

特點:
1.線程安全
2.自由串行化
3.保證只有一個單例
4.Effective java 作者Josh Bloch提倡的方式

/**
 * Effective java 作者Josh Bloch提倡的方式,解決了以下三個問題
 * 一、自由串行化
 * 二、保證只有一個單例
 * 三、線程安全
 */
public enum  SingleTonDemo5 {
    INSTANCE;

    public static void main(String[] args) {
        SingleTonDemo5 instance = SingleTonDemo5.INSTANCE;

    }


}

雙重校驗鎖

特點:
1.通常線程安全,低概率線程不安全
2.併發情況下,可能會出現線程未拿到實例返回null的情況。

/**
 * 通常線程安全,低概率線程不安全
 *
 * 當併發情況下
 * 一、線程A進入getInstance()方法,此時單例還沒有實例化,進入了鎖定塊
 * 二、線程B進入getInstance()方法,此時單例還沒有實例化,得以訪問接下來代碼塊,但是代碼塊已經被線程A鎖定
 * 三、線程A進入下一判斷,因爲單例還沒有實例化,所以實例化單例,實例化後退出代碼塊,解除鎖定
 * 四、線程B進入下一判斷,此時單例已經被實例化,退出代碼塊,解除鎖定
 * 五、線程A拿到單例實例並返回,線程B未拿到實例返回null
 *
 */
public class SingleTonDemo6 {

    private static SingleTonDemo6 singleTonDemo6;

    private SingleTonDemo6(){}

    public static SingleTonDemo6 getInstance(){
        if (singleTonDemo6 == null){
            synchronized (SingleTonDemo6.class){
                if (singleTonDemo6 == null){
                    singleTonDemo6 = new SingleTonDemo6();
                }
            }
        }
        return singleTonDemo6;
    }
}

加volatile的雙重校驗鎖

特點:
1.volatile關鍵字此處的作用是防止指令重排

/**
 * volatile關鍵字此處的作用是防止指令重排,把singleTonDemo7聲明爲volatile後,對它的寫操作就會
 * 有一個內存屏障,這樣在它賦值完成之前,都不會調用讀操作。
 *
 * 注意:volatile阻止的不是singleTonDemo7 = new SingleTonDemo7(),而是在寫操作完成之前,不會調用
 * if (singleTonDemo7 == null)這個讀操作
 */
public class SingleTonDemo7 {

    private static volatile SingleTonDemo7 singleTonDemo7;

    private SingleTonDemo7(){}

    public static SingleTonDemo7 getInstance(){
        if (singleTonDemo7 == null){
            synchronized (SingleTonDemo7.class){
                if (singleTonDemo7 == null){
                    singleTonDemo7 = new SingleTonDemo7();
                }
            }
        }
        return  singleTonDemo7;
    }

}

ThreadLocal

特點:
線程安全
隔離多個線程對數據的訪問衝突

/**
 * 線程安全
 * ThreadLocal會爲每個線程提供一個獨立的變量副本,從而隔離了多個線程對數據的訪問衝突
 *
 *
 */
public class SingleTonDemo8 {

    private static final ThreadLocal<SingleTonDemo8> THREAD_LOCAL =
            new ThreadLocal<SingleTonDemo8>(){
                @Override
                protected SingleTonDemo8 initialValue() {
                    return new SingleTonDemo8();
                }
    };
    private SingleTonDemo8(){}

    public static SingleTonDemo8 getInstance(){
        return THREAD_LOCAL.get();
    }
}

CAS鎖

特點:
線程安全

/**
 * 線程安全
 *
 * CAS機制當中使用了3個基本操作數:內存地址V,舊的預期值A,要修改的新值B。
 *更新一個變量的時候,只有當變量的預期值A和內存地址V當中的實際值相同時,纔會將內存地址V對應的值修改爲B。
 * CAS屬於樂觀鎖,樂觀地認爲程序中的併發情況不那麼嚴重,所以讓線程不斷去嘗試更新
 *
 * 缺點:
 * CPU開銷較大
 * CAS機制所保證的只是一個變量的原子性操作,而不能保證整個代碼塊的原子性
 */
public class SingleTonDemo9 {

    private static final AtomicReference<SingleTonDemo9> ATOMIC_REFERENCE = new AtomicReference<>();

    private SingleTonDemo9(){}

    public static final SingleTonDemo9 getInstance(){
        // 相當於while(true)
        for (;;){
            SingleTonDemo9 currentDemo9 = ATOMIC_REFERENCE.get();
            if (currentDemo9 != null){
                return currentDemo9;
            }
            currentDemo9 = new SingleTonDemo9();
            if (ATOMIC_REFERENCE.compareAndSet(null,currentDemo9)){
                return currentDemo9;
            }
        }
    }

    public static void main(String[] args) {
        SingleTonDemo9 testSingleTon = new SingleTonDemo9();
        SingleTonDemo9 testSingleTon2 = new SingleTonDemo9();
        System.out.println(testSingleTon == testSingleTon2);    //false
    }
}

Github地址


SingleTonDemo

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章