單例模式,簡而言之就是在整個應用程序裏面有且僅有一個實例,在程序的任什麼時候候,任何地方獲取到的該對象都是同一個對象。單例模式解決了一個全局的類被頻繁建立和銷燬的,或者每次建立或銷燬都須要消耗大量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);
}
}
推薦寫法,簡單高效。充分利用枚舉類的特性,只定義了一個實例,且枚舉類是自然支持多線程的。