單例模式-懶漢式和餓漢式,懶漢式的DCL以及volatile

一、簡介

單例模式,指的是某一個類,只允許實例出一個對象存在。而實現單例模式有懶漢式和餓漢式。餓漢式指的是在創建類時就初始化好對象,,而懶漢式指的是在需要使用到對象實例時,才進行初始化對象。

二、實現方式

餓漢式:

/**
 * 餓漢式單例模式
 */
public class HungerSingleton {

    private static HungerSingleton instance=new HungerSingleton();

    private HungerSingleton(){}

    public static HungerSingleton getInstance(){
        return instance;
    }
}

懶漢式(不考慮線程安全):

/**
 * 懶漢式單例
 */
public class LazySingleton {

    private static LazySingleton instance=null;

    private LazySingleton(){
        System.out.println("初始化LazySingleton..........");
    }

    public static LazySingleton getInstance(){
        if(instance==null)
            instance=new LazySingleton();
        return instance;
    }
}

三、懶漢式的線程不安全

懶漢式創建單例,在需要時才創建對象,因此在空間上更加友好。但也存在多線程的問題,比如線程1調用getInstance執行到

instance=new LazySingleton();

線程2也調用getInstance,並且判斷出instance==null,也同樣執行

instance=new LazySingleton();

這就導致了多線程下可能創建多個對象,這就不符合我們的預期了,測試代碼如下:

public class Singleton {
    public static void main(String[] args) {
        for(int i=0;i<20;i++){
            new Thread(new Runnable() {
                public void run() {
                    LazySingleton.getInstance();
                }
            }).start();
        }
    }
}

四、DCL雙端檢測鎖

使用雙端檢測鎖,解決這種問題,代碼如下:

/**
 * 懶漢式單例
 */
public class LazySingletonNew {

    private static LazySingletonNew instance=null;

    private LazySingletonNew(){
        System.out.println("初始化LazySingletonNew..........");
    }

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

測試代碼如下:

public class Singleton {
    public static void main(String[] args) {
        for(int i=0;i<20;i++){
            new Thread(new Runnable() {
                public void run() {
                    LazySingleton.getInstance();
                }
            }).start();
        }
        for(int i=0;i<20;i++){
            new Thread(new Runnable() {
                public void run() {
                    LazySingletonNew.getInstance();
                }
            }).start();
        }
    }
}

五、使用volatile+DCL

使用上述的DCL,似乎已經可以解決線程安全問題,而實際上上面的DCL仍然有出現問題的可能性,雖然說很小,分析如下:

instance=new LazySingletonNew();並非是原子操作,new創建對象分爲三步:

1、內存中分配一段空間

2、初始化該空間

3、將instance指向該空間

而由於指令可能存在重排(編譯器優化或者cpu優化),可能執行的順序爲1、 3、 2、

那麼,可能存在如下:

線程1創建空間,在synchronize代碼塊中,new對象的指令順序爲1、 3、 2,並且未執行到3時由於線程調度切換爲線程2,線程2再次判斷if(instance==null),發現不爲null,因此將未初始化的空間拿來用了。這時,調用一個未初始化的空間裏面的方法,變量名等將可能導致錯誤。

因此,使用volatile禁止指令重排。

代碼如下:

/**
 * 懶漢式單例
 */
public class LazySingletonNew {

    private static volatile LazySingletonNew instance=null;

    private LazySingletonNew(){
        System.out.println("初始化LazySingletonNew..........");
    }

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

 

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