之前只會寫固定的單例模式难述,沒有仔細(xì)研究過。最佳在書上看到介紹一步步單例模式吐句。不過是用cpp寫的胁后,與是自己用java一步步實現(xiàn)一遍。
Step1 適應(yīng)于單線程的Singleton
public class Singleton {
private Singleton() {}
private static Singleton INSTANCE=null;
public static Singleton getInstance(){
if (INSTANCE==null) {
INSTANCE=new Singleton();
}
return INSTANCE;
}
}
我們將構(gòu)造方法設(shè)為private避免了類在外部被實例化嗦枢,這樣在同一個虛擬機(jī)范圍內(nèi)攀芯,Singleton的唯一實例只能通過getInstance()方法訪問。并且我們的方法和成員變量都是靜態(tài)的文虏。
不足
這種方法如果在單線程中使用是能夠正常運(yùn)行的侣诺,但是如果我們是在多線程情況下呢?想一想氧秘,如果有兩個或以上的同學(xué)運(yùn)行到判斷instance是否為null的if語句的時候年鸳,這個時候如果instance沒有創(chuàng)建,那么這些線程都會創(chuàng)建instance丸相。這是就不滿足我們的單例要求了搔确。
Step2 在多線程下的Singleton
public class Singleton {
private Singleton() {}
private volatile static Singleton INSTANCE = null;
public static synchronized Singleton getInstance(){
if (INSTANCE==null) {
INSTANCE=new Singleton();
}
return INSTANCE;
}
}
不細(xì)心的你可能會說這兩行代碼不是一樣的嗎。不對灭忠,我們對getInstance方法實現(xiàn)了同步鎖膳算。此時如果有多個線程想創(chuàng)建一個實例,因為在同一時刻只能由一個線程得到同步鎖弛作,當(dāng)?shù)谝粋€線程加上鎖時涕蜂,后面的線程就需要等待。當(dāng)?shù)谝粋€線程發(fā)現(xiàn)instance還沒有創(chuàng)建的時候就會去創(chuàng)建映琳。接著第一個線程釋放同步鎖机隙,后面的線程加上同步鎖蜘拉,這時候?qū)嵗呀?jīng)由第一個線程創(chuàng)建了,所有第二個線程就不會重復(fù)的去創(chuàng)建了有鹿。
不足
雖然我們實現(xiàn)了多線程環(huán)境下的單例旭旭,但是你會發(fā)現(xiàn)我們每次在通過getInstance獲取實例時,我們都視圖去加上一個鎖印颤,但是加鎖是一個耗時操作您机,我們應(yīng)該盡量避免它穿肄。
Step3 避免加鎖帶來的耗時
我們只需要當(dāng)我們的實例還沒有創(chuàng)建的時候進(jìn)行加鎖防止多個線程同時創(chuàng)建實例年局,當(dāng)實例已經(jīng)創(chuàng)建的時候我們就應(yīng)該避免加鎖。
public class Singleton {
private Singleton() {}
private volatile static Singleton INSTANCE = null;//使用volatile修飾禁止java重排序
public static Singleton getInstance(){
if (INSTANCE==null) {
synchronized (Singleton.class){
if (INSTANCE==null) { //二次檢測
INSTANCE=new Singleton();
}
}
}
return INSTANCE;
}
}
我們在加鎖前進(jìn)行一次判斷咸产,這樣只有當(dāng)instance不存在的時候才需要加鎖操作矢否。這樣我們的單例寫的比較完美了,但是if語句的判斷容易增加我們代碼的錯誤率脑溢,精益求精的我們肯定不止步于此僵朗,再來想想更加優(yōu)秀的解法。
一開始我是沒有使用volatile修飾符的屑彻,這樣可能出現(xiàn)一個問題验庙,在另一個線一中看到以個初始化了一半的instance的情況,但使用了volatile變量后社牲,就能保證先行發(fā)生關(guān)系(happens-before relationship)粪薛。對于volatile變量_instance,所有的寫(write)都將先行發(fā)生于讀(read)搏恤,在Java 5之前不是這樣违寿,所以在這之前使用雙重檢查鎖有問題。有了先行發(fā)生的保障(happens-before guarantee)熟空,可以認(rèn)為它是安全的
推薦解法一
public class Singleton {
private Singleton() {}
public static final Singleton getInstance(){
return MyInstance.INSTANCE;
}
private static class MyInstance{
private static final Singleton INSTANCE=new Singleton();
}
}
對了藤巢,上沒的幾種方法和解法一都是我們常說的懶漢模式。懶漢模式實現(xiàn)的是按需加載的單例模式息罗。只有當(dāng)我們需要的時候調(diào)用getInstance方法才會實例化這個單例掂咒。
推薦解法二
public class Singleton{
private Singleton() {}
private static final Singleton INSTANCE=new Singleton();
public static final Singleton getInstance(){
return INSTANCE;
}
}
上面這種就是餓漢模式了,餓漢就是類一旦加載迈喉,就把單例初始化完成俏扩,保證getInstance的時候,需要的實例是已經(jīng)存在的了弊添。因為餓漢模式在類創(chuàng)建的同時就已經(jīng)創(chuàng)建好一個靜態(tài)的對象供系統(tǒng)使用录淡,以后不再改變,所以天生是線程安全的油坝。
推薦解法三
public enum Singleton{
INSTANCE嫉戚;
}
除過枚舉實現(xiàn)的單例模式以外的其他實現(xiàn)方式都有一個比較大的問題是一旦實現(xiàn)了 Serializable 接口后就不再是單例了刨裆,因為每次調(diào)用 readObject() 方法返回的都是一個新創(chuàng)建出來的對象(當(dāng)然可以通過使用 readResolve() 方法來避免,但是終歸麻煩)彬檀,而 Java 規(guī)范中保證了每一個枚舉類型及其定義的枚舉變量在 JVM 中都是唯一的帆啃,在枚舉類型的序列化和反序列化上 Java 做了特殊處理,序列化時 Java 僅僅是將枚舉對象的 name 屬性輸出到結(jié)果中窍帝,反序列化時則是通過 java.lang.Enum 的 valueOf 方法來根據(jù)名字查找枚舉對象努潘,同時禁用了 writeObject、readObject坤学、readObjectNoData疯坤、writeReplace 和 readResolve 等方法。參考這里