java單例

我們先寫一個God類吧,類中空空如也,世界如此清淨,虛無縹緲。

 

public class God {

}

 

首先我們得保證任何人都不能去創建神的實例,否則如:new God(),這樣世界又要陷入戰爭的災難,各種造神運動,或是某天又出來個什麼神棍先知告訴信徒說他們肚子裏有個輪子。那就不寫構造方法吧?不行,因爲有默認的無參構造器!那就把構造方法改成private吧,也就是神可以自己創造自己,但別人不能。

 

public class God {
    private God(){}//構造方法私有化
}

 

God類裏面封裝一個God自己,對,一切都是神創造的,包括我們人類。有人開始質疑,那神是誰?神自己是誰造的?這是個哲學問題。神說“I am who I am.” 我是我所是,我就是我,自有永有,超越時空。很逆天吧? 好吧,誰也不能造上帝,神自己造自己。

 

public class God {
    private static final God god = new God();//自有永有的神單例
    private God(){}//構造方法私有化
}

 

以上private關鍵字保證了上帝的私有性,不可見性,不可訪問性,我想沒有活人見過上帝吧?static關鍵字保證上帝的靜態性,他與類同在,不依賴於類的實例化就自有永有,他將在內存中永生,GC垃圾回收器也回收不了他。final關鍵字則保證這位神是和常量,衡量,他是終極上帝,不能再改。

 

正如同靜態方法main(),不需要實例化類就能運行的入口,同樣我們需要一個靜態方法getInstance()來請神,方法體內我們就返回這個在唯一的真神,當然方法它必須是public公開的,不然誰都訪問不了。

 

public class God {
    private static final God god = new God();//自有永有的神單例
    private God(){}//構造方法私有化
    public static God getInstance(){//請神方法公開化
        return god;
    }
}

 

以上的神類雛形已經寫好了,當然你還可以加其他的功能方法,比如說創世紀神造了光,造了世界、動物、人、亞當夏娃等等功能,我們這裏就不在贅述了。那對於外部來說只要調用God.getInstance();就可以拿到神了,而且不管誰拿,拿幾次,都是同一個神,這樣就保證了整個系統中神的唯一性,不可僞造性,至於其他先知那也只是神的代理人,只能幫請神而已。

 

好了,其實我們已經學會了單例模式的“癡漢模式(Eager load)”,代碼第一行一開始就造出了神(new God那一句),已經準備好了隨時給你請神,這樣就有了一個問題,如果沒人請神那不是白造了?提前備貨如果價格跌了不是很慘?反應在系統中的問題就是佔用了內存空間。於是又有了“懶漢模式(Lazy load)”

 

public class God {
    private static God god;//這裏不進行實例化
    private God(){}
    public static God getInstance() {
        if (god == null) {//如果無神才造神
            god = new God();
        }
        return god;
    }
}

 

這我們看到一開始就沒有造神,只有某人第一次求神時才實例化,之後再求的就直接返回了。這樣的好處是省了一段時間的內存(無求神期間),壞處是第一次請神的時候速度相較之前的癡漢模式會慢,因爲要消耗CPU去造神。

 

其實這麼寫是在多線程模式下是有陷阱的,試想多人同時併發請神的話,依然會造成多神,好吧我們再來改良一下,把請神方法加上synchronized,聲明爲同步方法,某線程調用前必須獲取同步鎖,調用完後會釋放鎖給其他線程用,也就是請神的必須排隊,大家一個一個按順序來。

 

public class God {
    private static God god;//這裏不進行實例化
    private God(){}
    public static synchronized God getInstance() {//此處加入同步
        if (god == null) {//如果無神才造神
            god = new God();
        }
        return god;
    }
}

 

然而,這樣做是要付出代價的,還沒進廟呢不管三七二十一請神的直接給加鎖排隊,結果隊伍從北邊的廟排到了南天門,人們都要來一個一個拜佛求神,這造成了巨大時間浪費,沒有充分利用CPU資源併發優勢(特別是多核情況)。好吧,那還是讓人們搶好了,但依然得保證單例神的情況下。

 

 

這裏我們去掉方法上的同步關鍵字,換到方法體內部做同步,整個方法開放併發大家都可以同時入廟,當然起早貪黑的虔誠信徒們要搶頭香是必須要入堂排隊的。一旦頭香誕生,那其他搶香的都白早起,白排隊了。再之後的事情我們都可以預見了,頭注香被搶後堂內排隊再無必要來了,大家可以在堂外同時併發拜佛求神,這就極大的利用了CPU資源。簡而言之:只有第一批搶頭香的在排隊,之後大家都不必排隊了,代碼如下。

 

public class God {
    private volatile static God god;
    private God(){} 
    public static God getInstance() {//廟是開放的不用排隊進入
        if (god == null) {//如果頭柱香未產生,這批搶香人進入堂內排隊。
            synchronized(God.class){
                if (god == null) {//只有頭香造了神,其他搶香的白排隊了
                    god = new God();
                }
            }
        }
        //此處頭柱香產生後不必再排隊
        return god;
    }
}

 

其實在這之上還發展出了各種各樣的單例模式變種,我們這裏只講了最基礎的兩種,其實他們都各有優缺,我們要做到靈活運用,各取所需。對於我個人來講傾向於癡漢模式,現在內存成本根本不算問題,況且遲早要被實例化佔用內存,加鎖解鎖更是一種浪費,還有同步效率低等問題,如果上帝不是很佔空間那就沒必要去懶漢延遲加載,越複雜問題越多,風險越大。

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