程式設計師築基之路——如何寫好一個單例

窗邊的扁豆發表於2017-09-20

如何寫好一個單例

[TOC]

什麼是單例模式?

  • 如果你聽說過設計模式,那麼肯定知道單例模式,因為單例模式是設計模式中最簡單的一種。顧名思義:單例模式就是一個類只有一個例項變數的一種設計模式,通過使用單例模式,可以節約系統的資源開銷,避免共享資源的多重佔用等優點。
  • 什麼時候會用:
    1. 對於那種經常例項化但是過一會兒就被銷燬的物件適合使用單例模式。
    2. 對於建立物件需要消耗很多資源的物件。如:資料庫連線池物件,執行緒池物件等
    3. 只需要一個物件保證全域性的一致性的。如:Android中Application物件,網站的計數器等。

實現一個單例模式

  • 如果你是一位對設計模式略有接觸的新手,一定會毫不費力的就寫出了以下單例程式碼(懶漢式單例:等到需要時再例項化):

    /**
     * Created by forever on 2017/9/20.
     */
    public class Singleton {
        private static Singleton singleton;
    
        private Singleton() {
    
        }
    
        public static Singleton getInstance() {
            if (singleton == null) {
                singleton = new Singleton();
            }
            return singleton;
        }
    }
    複製程式碼

    其實上面的程式碼就已經涵蓋了單例模式最重要的三個要素:

    1. 將構造方法私有化(保證外部不能直接構造)。
    2. 有一個靜態屬性指向例項
    3. 提供一個公有的靜態方法向外面提供這個例項。
  • 然而表面看似完美的程式碼,內部其實暗藏殺雞:

    這裡寫圖片描述

    在單執行緒中看似是沒有什麼問題的,但如果放在多執行緒的環境中就會有問題了。加入有兩個執行緒同時訪問getInstance方法,如果期中一個執行緒剛進入if (singleton == null){}裡面,這個時候另一個執行緒恰好也訪問這個方法,並且完成建立了一個例項,那個剛剛掛起的那個執行緒繼續執行的話就會再建立一個例項。那我們單例的理想不就破滅的了嘛。

    這裡寫圖片描述

基本方法的改進

  • 既然瞭解了問題,那麼我們如何才能防止兩個執行緒同時例項化方法呢?有經驗的同學或許就會立刻想到了Java的同步。通過synchronized關鍵字進行加鎖。

    public synchronized Singleton getInstance() {
            if (singleton == null) {
                singleton = new Singleton();
            }
            return singleton;
        }
    複製程式碼
  • 不過加鎖的話對程式的效能效能會有很大影響,如果當某個執行緒正在訪問該方法的時候其他執行緒就只能在鎖池中等待該執行緒釋放鎖,我們稍加改進一下:

    public Singleton getInstance() {
            if (singleton == null) {
                synchronized (Singleton.class){
                    if(singleton == null){
                        singleton = new Singleton();
                    }
                }
            }
            return singleton;
        }
    複製程式碼

    這樣只在構造例項程式碼的時候加鎖,對程式的效能影響就小多了。而且只要例項化完成之後,後面基本就不會進入這個同步程式碼塊了。

  • 看似已經很完美了,那麼我們有什麼其他辦法不用加鎖的方式也能避免多執行緒的問題呢?ok,當然有的,我們可以使用餓漢式的單例:

    /**
     * Created by forever on 2017/9/20.
     */
    public class Singleton {
        public Singleton singleton = new Singleton();
    
        private Singleton() {
        }
    
        public Singleton getInstance() {
            return singleton;
        }
    }
    複製程式碼

    上面程式碼與懶漢式載入最大的區別在於這裡的single在開始就例項化了,也就是不管我們是否使用它,都會將其載入到記憶體中去。這個在獲取的時候就直接返回就行了。如果不在意記憶體的話最好使用這個方法。

  • 如果你說,我既不想要使用同步,但又十分在意記憶體資源怎麼辦,ok,說明你是一個很有追求的人,其實在也是有辦法的(辦法總比問題多):

    public class Singleton {
    
        private Singleton(){
    
        }
    
        public static Singleton getInstance(){
            return Nested.singleton;
        }
    
        public static class Nested{
            static {
                System.out.println("蛤蛤");
            }
            private static Singleton singleton = new Singleton();
        }
    }
    複製程式碼

    這個時候我們就需要一個內部類作為橋樑了,當我們getInstance()時,類載入器才會去載入Nested,然後例項化Singleton的例項,如果你對Java的類載入機制有了解的話一定很容易就理解了上述程式碼。

總結

  • Java的單例模式看似簡單,其實深究而言還是有很多值得思考的東西的,如果在面試的時候碰到了也可以和麵試官多吹逼一會兒。最近一直在準備校招而且還要完成學校的實習,作為菜鳥表示真的很忙,也祝願大家都能找到滿意的工作。如果發現寫的有什麼問題歡迎指正,希望與大家共同進步。

相關文章