1. 程式人生 > >設計模式(Java語言)-單例模式

設計模式(Java語言)-單例模式

  單例模式,簡而言之就是在整個應用程式裡面有且僅有一個例項,在程式的任何時候,任何地方獲取到的該物件都是同一個物件。單例模式解決了一個全域性的類被頻繁建立和銷燬的,或者每次建立或銷燬都需要消耗大量cpu資源的物件的問題。單例模式總的可以分為懶漢模式和餓漢模式,顧名思義,懶漢模式是一個非常懶的漢子,只要你沒有使用到它,它就永遠不會例項化。餓漢模式的意思就是,漢子非常飢渴,只要在程式的編譯階段就給你分配記憶體,建立好物件。

  將懶漢模式和餓漢模式細分,又可以分為:

  1、懶漢模式

  2、餓漢模式

  3、雙檢模式

  4、靜態內部類模式

  5、列舉模式

  不管是用哪一種方式實現的單例模式,其建立流程基本都是一直的:首先將構造方法宣告為private的,這樣就防止直接new出一個新的物件。第二,宣告一個私有的成員變數,即單例物件。第三步,宣告一個public的靜態方法,用於獲取或建立單例物件,外部想要獲取該物件必須通過這個方法獲取。

  一、懶漢模式1--執行緒安全

/**
 * 餓漢模式1
 */
public class HungrySingleton1 {

    private static HungrySingleton1 singleton = new HungrySingleton1();

    private HungrySingleton1(){}

    public static HungrySingleton1 getInstance() {
        return singleton;
    }


}

  這種懶漢模式的優點是實現非常簡單。缺點是並起到懶載入的效果,如果專案沒有使用到這個物件的就會造成資源的浪費。

 

  二、餓漢模式1--執行緒不安全

/**
 * 懶漢模式1
 */
public class LazySingleton1 {

    private static LazySingleton1 singleton;

    private LazySingleton1(){}

    public static LazySingleton1 getInstance() {
        if (singleton == null) {
            singleton = new LazySingleton1();
        }
        return singleton;
    }


}

  這種寫法雖然實現了懶載入的效果,但是嚴格意義上並不是單例模式,因為在多執行緒的環境下有可能會創建出多個不同的物件,至於為什麼,不懂的可以看一下我之間寫的關於Java記憶體模型的文章。這種寫法只能應用於單執行緒的環境下,侷限性很大。實際中強烈不建議使用這種方法。

 

  三、懶漢模式2--執行緒安全

  

/**
 * 懶漢模式2
 */
public class LazySingleton2 {

    private static LazySingleton2 singleton;

    private LazySingleton2(){}

    public static synchronized LazySingleton2 getInstance() {
        if (singleton == null) {
            singleton = new LazySingleton2();
        }
        return singleton;
    }


}

  這種寫法咋看跟上面的方法一樣,這種寫法在方法上添加了 synchronized  關鍵字,這樣就保證了每次只能有一個執行緒進入方法體中,解決了懶漢模式1中出現的問題。這種寫法的優點是實現了懶載入的效果,缺點是效率非常低,當多個執行緒同時獲取例項時,有可能會造成執行緒阻塞的情況。不推薦使用。

 

  懶漢模式3--執行緒不安全

/**
 * 懶漢模式3
 */
public class LazySingleton3 {

    private static LazySingleton3 singleton;

    private LazySingleton3(){}

    public static LazySingleton3 getInstance() {
        if (singleton == null) {
            synchronized (LazySingleton3.class) {
                if (singleton == null) {
                    singleton = new LazySingleton3();
                }
            }
        }
        return singleton;
    }
}

  這種寫法進行了兩次 singleton == null 的判斷,在實際的應用中當我們呼叫這個方法時,其實99%的機率是例項就已經建立好了,因此第一個 singleton == null 能過濾掉99%的呼叫,不用將方法鎖起來,從而提高了效率。這種方法的優點是實現懶載入的效果,效率和很高。缺點是程式碼設計仍然後缺陷,jvm在為物件分配記憶體和賦值並不是一個原子操作,即 singleton = new LazySingleton3() 這段程式碼在jvm中是由三個步驟實現的,首先jvm會在堆中為物件分配一定的記憶體空間,然後完成物件的初始化工作,然後將記憶體地址指向到物件中。但是,我們知道,jvm在編譯的時候並不總是根據我們編寫的程式碼的順序來執行了,而是根據jvm覺得最優的順序執行(這個過程就叫做指令重排序),所以有可能在執行了步驟1後就執行了步驟3,這時候第二個執行緒進來的發現singleton並不為空,因此就直接返回了該物件,因此造成空指標異常。

 

  四、雙重檢查鎖模式---執行緒安全

/**
 * 懶漢模式4
 */
public class LazySingleton4 {

    private volatile static LazySingleton4 singleton;

    private LazySingleton4(){}

    public static LazySingleton4 getInstance() {
        if (singleton == null) {
            synchronized (LazySingleton4.class) {
                if (singleton == null) {
                    singleton = new LazySingleton4();
                }
            }
        }
        return singleton;
    }
}

  相較於上面的方式,這種方式只是在成員變數中添加了 volatile  關鍵字,解決了指令重排序的問題,同時確保當前執行緒修改了這個變數時,其他的執行緒能夠及時讀到最新的值。這種方法缺點是寫起來比較複雜,要求程式設計師對jvm比較理解。優點是既保證了執行緒安全,同時也能夠保證了比較高的效率。

 

  五、靜態內部類模式--執行緒安全

/**
 * 懶漢模式5
 */
public class LazySingleton5 {

    private LazySingleton5(){}

    private static class Holder {
        private static final LazySingleton5 INSTANCE = new LazySingleton5();
    }

    public static LazySingleton5 getInstance() {
        return Holder.INSTANCE;
    }

}

  這種寫法實現比較簡單,即實現了懶載入的效果,同時也保證的多執行緒環境下的執行緒安全問題。推薦使用這種方式。

 

  六、列舉模式 -- 執行緒安全

  

/**
 * 懶漢模式6
 */
public enum  LazySingleton6 {

   INSTANCE

}

//使用方法
public class Test {

public static void main(String[] args) {
LazySingleton6 instance = LazySingleton6.INSTANCE;
LazySingleton6 instance1 = LazySingleton6.INSTANCE;
System.out.println(instance == instance1);

}

}

  推薦寫法,簡單高效。充分利用列舉類的特性,只定義了一個例項,且列舉類是天然支援多執行緒