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

  單例模式,簡而言之就是在整個應用程序裏面有且僅有一個實例,在程序的任什麼時候候,任何地方獲取到的該對象都是同一個對象。單例模式解決了一個全局的類被頻繁建立和銷燬的,或者每次建立或銷燬都須要消耗大量cpu資源的對象的問題。單例模式總的能夠分爲懶漢模式和餓漢模式,顧名思義,懶漢模式是一個很是懶的漢子,只要你沒有使用到它,它就永遠不會實例化。餓漢模式的意思就是,漢子很是飢渴,只要在程序的編譯階段就給你分配內存,建立好對象。java

  將懶漢模式和餓漢模式細分,又能夠分爲:程序員

  一、懶漢模式安全

  二、餓漢模式多線程

  三、雙檢模式jvm

  四、靜態內部類模式線程

  五、枚舉模式設計

  不論是用哪種方式實現的單例模式,其建立流程基本都是一直的:首先將構造方法聲明爲private的,這樣就防止直接new出一個新的對象。第二,聲明一個私有的成員變量,即單例對象。第三步,聲明一個public的靜態方法,用於獲取或建立單例對象,外部想要獲取該對象必須經過這個方法獲取。指針

  1、懶漢模式1--線程安全對象

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

    private static HungrySingleton1 singleton = new HungrySingleton1();

    private HungrySingleton1(){}

    public static HungrySingleton1 getInstance() {
        return singleton;
    }


}

  這種懶漢模式的優勢是實現很是簡單。缺點是並起到懶加載的效果,若是項目沒有使用到這個對象的就會形成資源的浪費。blog

 

  2、餓漢模式1--線程不安全

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

    private static LazySingleton1 singleton;

    private LazySingleton1(){}

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


}

  這種寫法雖然實現了懶加載的效果,可是嚴格意義上並非單例模式,由於在多線程的環境下有可能會建立出多個不一樣的對象,至於爲何,不懂的能夠看一下我之間寫的關於Java內存模型的文章。這種寫法只能應用於單線程的環境下,侷限性很大。實際中強烈不建議使用這種方法。

 

  3、懶漢模式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、雙重檢查鎖模式---線程安全

/**
 * 懶漢模式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、靜態內部類模式--線程安全

/**
 * 懶漢模式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、枚舉模式 -- 線程安全

  

/**
 * 懶漢模式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);

}

}

  推薦寫法,簡單高效。充分利用枚舉類的特性,只定義了一個實例,且枚舉類是自然支持多線程的。

相關文章
相關標籤/搜索