單例模式的五種寫法

懶漢:

線程不安全

public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  

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

線程安全:

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

這種寫法能夠在多線程中很好的工作,而且看起來它也具備lazy loading,但是,遺憾的是,效率很低,99%情況下不需要同步。

餓漢:

public class Singleton {  
    private static Singleton instance = new Singleton();  
    private Singleton (){}  
    public static Singleton getInstance() {  
        return instance;  
    }  
}  

這種方式基于classloder機制,避免了多線程的同步問題,不過,instance在類裝載時就實例化,雖然導(dǎo)致類裝載的原因有很多種,在單例模式中大多數(shù)都是調(diào)用getInstance方法, 但是也不能確定有其他的方式(或者其他的靜態(tài)方法)導(dǎo)致類裝載,這時候初始化instance顯然沒有達(dá)到lazy loading的效果。

餓漢,變種

public class Singleton {  
    private static Singleton instance = null;  
    static {  
        instance = new Singleton();  
    }  
    private Singleton (){}  
    public static Singleton getInstance() {  
        return this.instance;  
    }  
}  

表面上看起來差別挺大,其實跟上面的一種差不多,都是在類初始化即實例化instance。

靜態(tài)內(nèi)部類:

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

這種方式同樣利用了classloder的機制來保證初始化instance時只有一個線程,它跟餓漢方式不同的是(很細(xì)微的差別):餓漢是只要Singleton類被裝載了,那么instance就會被實例化(沒有達(dá)到lazy loading效果),而這種方式是Singleton類被裝載了,instance不一定被初始化。因為SingletonHolder類沒有被主動使用,只有顯示通過調(diào)用getInstance方法時,才會顯示裝載SingletonHolder類,從而實例化instance。想象一下,如果實例化instance很消耗資源,我想讓他延遲加載,另外一方面,我不希望在Singleton類加載時就實例化,因為我不能確保Singleton類還可能在其他的地方被主動使用從而被加載,那么這個時候?qū)嵗痠nstance顯然是不合適的。這個時候,這種方式相比餓漢模式就顯得很合理。

枚舉:

public enum Singleton {  
    INSTANCE;  
    public void whateverMethod() {  
    }  
}  

這種方式是< Effective Java>作者Josh Bloch 提倡的方式,它不僅能避免多線程同步問題,而且還能防止反序列化重新創(chuàng)建新的對象,可謂是很堅強的壁壘啊。不過,個人認(rèn)為由于1.5中才加入enum特性,用這種方式寫不免讓人感覺生疏,也很少看見有人這么寫過。

雙重校驗鎖:

public class Singleton {  
    private static Singleton singleton;  
    private Singleton (){}  
    public static Singleton getSingleton() {  
        if (singleton == null) {  
            synchronized (Singleton.class) {  
                if (singleton == null) {  
                    singleton = new Singleton();  
                }  
            }  
        }  
        return singleton;  
    }  
}  

雙重檢驗鎖模式(double checked locking pattern),是一種使用同步塊加鎖的方法。程序員稱其為雙重檢查鎖,因為會有兩次檢查 instance == null,一次是在同步塊外,一次是在同步塊內(nèi)。為什么在同步塊內(nèi)還要再檢驗一次?因為可能會有多個線程一起進(jìn)入同步塊外的 if,如果在同步塊內(nèi)不進(jìn)行二次檢驗的話就會生成多個實例了。
這段代碼看起來很完美,很可惜,它是有問題。主要在于instance = new Singleton()這句,這并非是一個原子操作,事實上在 JVM 中這句話大概做了下面 3 件事情。

  1. 給 instance 分配內(nèi)存
  2. 調(diào)用 Singleton 的構(gòu)造函數(shù)來初始化成員變量
  3. 將instance對象指向分配的內(nèi)存空間(執(zhí)行完這步 instance 就為非 null 了)

但是在 JVM 的即時編譯器中存在指令重排序的優(yōu)化。也就是說上面的第二步和第三步的順序是不能保證的,最終的執(zhí)行順序可能是 1-2-3 也可能是 1-3-2。如果是后者,則在 3 執(zhí)行完畢、2 未執(zhí)行之前,被線程二搶占了,這時 instance 已經(jīng)是非 null 了(但卻沒有初始化),所以線程二會直接返回 instance,然后使用,然后順理成章地報錯。
我們只需要將 instance 變量聲明成 volatile 就可以了。

public class Singleton {
    private volatile static Singleton instance; //聲明成 volatile
    private Singleton (){}

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

使用volatile 的主要原因是:禁止指令重排序優(yōu)化。

總結(jié)

個人而言,一般情況下直接使用餓漢式就好了,如果明確要求要懶加載(lazy initialization)會傾向于使用靜態(tài)內(nèi)部類,如果涉及到反序列化創(chuàng)建對象時會試著使用枚舉的方式來實現(xiàn)單例。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容