Java設計模式——單例模式

1.餓漢式:靜態常量html

這種方法很是的簡單,由於單例的實例被聲明成static和final變量了,在第一次加載類到內存中時就會初始化,因此會建立實例自己是線程安全的。安全

public class Singleton1 {  

    private final static Singleton1 instance = new Singleton1();  
    private Singleton1(){}  
    public static Singleton1 getInstance(){  
        return instance;  
    }  
}

它的缺點是否是一種懶加載,單例會在加載類後一開始就被初始化,即便客戶端沒有調用getInstance()方法。餓漢式的建立方式在一些場景中將沒法使用:好比Singleton實例的建立是依賴參數或者配置文件的,在getInstance()以前必須調用某個方法設置參數給它,那麼單例寫法就沒法使用了。
2.懶漢式:線程不安全多線程

public class Singleton3 {  

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

這裏使用了懶加載模式,可是卻存在致命的問題。當多個線程並行調用getInstance()的時候,就會建立多個實例,即在多線程下不能正常工做。
3.懶漢式:線程安全ide

public class Singleton4 {  

    private static Singleton4 instance;  
    private Singleton4(){}  
    public static synchronized Singleton4 getInstance(){  
        if(instance == null){  
            instance = new Singleton4();  
        }  
        return instance;  
    }  
}```
雖然作到了線程安全,而且解決了多實例的問題,可是它並不高效。由於在任什麼時候候只能有一個線程調用getInstance()方法,可是同步操做只須要在第一次調用時才被須要,即第一次建立單例實例對象時。
4.懶漢式:靜態內部類

public class Singleton5 { 函數

private static class SingletonHandler{  
    private static final Singleton5 INSTANCE = new Singleton5();  
}  
private Singleton5(){}  
public static Singleton5 getInstance(){  
    return SingletonHandler.INSTANCE;  
}

}```
這種寫法仍然使用JVM自己機制保證了線程安全問題;因爲SingletonHandler是私有的,除了getInstacne()以外沒有辦法訪問它,所以它是懶漢式的;同時讀取實例的時候不會進行同步,沒有性能缺陷,也不依賴JDK版本。
5.雙重檢驗鎖:性能

雙重檢驗模式,是一種使用同步塊加鎖的方法。又稱其爲雙重檢查鎖,由於會有兩次檢查instance == null,一次是在同步塊外,一次是在同步快內。爲何在同步塊內還要檢驗一次,由於可能會有多個線程一塊兒進入同步塊外的if,若是在同步塊內不進行二次檢驗的話就會生成多個實例了。優化

public class Singleton6 {  

    private static Singleton6 instance;  
    private Singleton6(){}  
    public static Singleton6 getSingleton6(){  
        if(instance==null){  
            synchronized(Singleton6.class){  
                if(instance==null){  
                   instance = new Singleton6();  
                }  
            }  
        }  
        return instance;  
    }  

}```
其中,instance = new Singleton6()並不是是原子操做,事實上在JVM中這句話作了三件事:
1.給instance分配內存

2.調用Singleton6的構造函數來初始化成員變量

3.將instance對象指向分配的空間(執行完這一步instance就爲null)

可是在JVM的即時編譯器中存在指令重排序的優化,也就是說上面的第二步和第三步是不能保證順序的,最終執行的順序多是1-2-3或者是1-3-2。若是是後者,則在3執行完畢,2執行以前,被線程2搶佔了,這時instance已是非null了(但卻沒有初始化),因此線程2會直接返回instance,而後使用,而後會報錯。

```public class Singleton6 {  

    private volatile static Singleton6 instance;  
    private Singleton6(){}  
    public static Singleton6 getSingleton6(){  
        if(instance==null){  
            synchronized(Singleton6.class){  
                if(instance==null){  
                   instance = new Singleton6();  
                }  
            }  
        }  
        return instance;  
    }  

}

有人認爲使用volatile的緣由是可見性,也就是能夠保證線程在本地不會存有instance副本,每次都是去主內存中讀取,可是實際上是不對的。使用volatile的主要緣由是其另外一個特性:禁止指令重排序優化。也就是說,在volatile變量的賦值操做後面會有一個內存屏障(生成的彙編代碼上),讀操做不會被重排序到內存屏障以前。好比上面的例子,取操做必須在執行完1-2-3以後或者1-3-2以後,不存在執行到1-3而後取到值的狀況。從[先行發生原則]的角度理解的話,就是對於一個volatile變量的寫操做都先行發生於後面對這個變量的讀操做。
注意:在Java5之前的版本使用了volatile的雙檢鎖仍是有問題的。其緣由是Java5之前的JMM(Java內存模型)是存在缺陷的,即時將變量聲明成volatile也不能避免重排序。線程

6.枚舉:code

public class Singleton7 {  

    public enum EasySingleton{  
        INSTANCE;  
    }  
}

咱們能夠經過EasySingleton.INSTANCE來訪問實例,這比調用getInstance()方法簡單多了。建立枚舉默認就是線程安全,並且還能防止反序列化致使從新建立新的對象。htm

更多幹貨教程地址:http://edu.51cto.com/lecturer/12509664.html和關注本帳號。

相關文章
相關標籤/搜索