摘要:單例模式概述單例模式是一種對象創(chuàng)建模式,用于產(chǎn)生一個類的具體事例。所以解決了線程安全問題參考失效原因和解決方案中單例模式的缺陷及單例的正確寫法懶漢式靜態(tài)內(nèi)部類私有構(gòu)造器獲取單例的方法靜態(tài)內(nèi)部類持有單例作為靜態(tài)屬性。
單例模式概述
單例模式是一種對象創(chuàng)建模式,用于產(chǎn)生一個類的具體事例。使用單例模式可以確保整個系統(tǒng)中單例類只產(chǎn)生一個實例。有下面兩大好處:
對于頻繁創(chuàng)建的對象,節(jié)省初第一次實例化之后的創(chuàng)建時間。
由于new操作的減少,會降低系統(tǒng)內(nèi)存的使用頻率。減輕GC壓力,從而縮短GC停頓時間
創(chuàng)建方式:
單例作為類的私有private屬性
單例類擁有私有private構(gòu)造函數(shù)
提供獲取實例的public方法
單例模式的角色:
角色 | 作用 |
---|---|
單例類 | 提供單例的工廠,返回類的單例實例 |
使用者 | 獲取并使用單例類 |
類基本結(jié)構(gòu):
public class HungerSingleton { //1.餓漢式 //私有構(gòu)造器 private HungerSingleton() { System.out.println("create HungerSingleton"); } //私有單例屬性 private static HungerSingleton instance = new HungerSingleton(); //獲取單例的方法 public static HungerSingleton getInstance() { return instance; } }
注意:
單例修飾符為static JVM加載單例類加載時,直接初始化單例。無法延時加載。如果此單例一直未被使用,單Singleton 因為調(diào)用靜態(tài)方法被初始化則會造成內(nèi)存的浪費。
getInstance()使用static修飾,不用實例化可以直接使用Singleton.getInstance()獲取單例。
由于單例由JVM加載類的時候創(chuàng)建,所以不存在線程安全問題。
2.簡單懶漢式public class Singleton { //2.1簡單懶漢式(線程不安全) //私有構(gòu)造器 private Singleton() { System.out.println("create Singleton"); } //私有單例屬性[初始化為null] private static Singleton instance = null; //獲取單例的方法 public static Singleton getInstance() { if(instance == null) { //此處instance實例化 //首次調(diào)用單例時會進(jìn)入 達(dá)成延時加載 instance = new Singleton(); } return instance; } }
由于未使用 synchronized 關(guān)鍵字,所以當(dāng)線程1調(diào)用單例工廠方法Singleton.getInstance() 且 instance 未初始化完成時,線程2調(diào)用此方法會將instance判斷為null,也會將instance重新實例化賦值,此時則產(chǎn)生了多個實例!
如需線程安全可以直接給getInstance方法上加synchronized關(guān)鍵字,如下:
public class Singleton { //2.2簡單懶漢式(線程安全) //私有構(gòu)造器 private Singleton() { System.out.println("create Singleton"); } //私有單例屬性[初始化為null] private static Singleton instance = null; //獲取單例的方法 將此方法使用synchronized關(guān)鍵字同步 public static synchronized Singleton getInstance() { if(instance == null) { //此處instance實例化 //首次調(diào)用單例時會進(jìn)入 達(dá)成延時加載 instance = new Singleton(); } return instance; } }
面臨的問題:
由于對getInstance()整個方法加鎖,在多線程的環(huán)境中性能比較差。
3.DCL 懶漢式(雙重檢測)簡單懶漢式(線程安全)中,對getInstance()方法加鎖,導(dǎo)致多線程中性能較差,那么是否可以減小鎖的范圍,使不用每次調(diào)用geInstance()方法時候都會去競爭鎖?DCL(Double Check Locking)雙重檢測 就是這樣一種實現(xiàn)方式。
public class DCLLazySingleton { //3.DCL //私有構(gòu)造器 private DCLLazySingleton() { System.out.println("create DCLLazySingleton"); } //私有單例屬性[初始化為null] volatile 保證內(nèi)存可見性 防止指令重排 private static volatile DCLLazySingleton instance = null;//step1 //獲取單例的方法 public static DCLLazySingleton getInstance() { //這里判null 是為了在instance有值時,不進(jìn)入加鎖的代碼塊,提高代碼性能。 if(instance == null) { //縮小鎖范圍 由于是靜態(tài)方法方法調(diào)用的時候不依賴于實例化的對象 加鎖只能使用類 synchronized (DCLLazySingleton.class) { //這里判null 是為了配合volatile解決多線程安全問題 if(instance == null) { instance = new DCLLazySingleton(); } } } return instance; } }
注意:
傳統(tǒng)DCL(step1處并未使用 volatile 或使用了但在JDK1.8之前)面臨的問題:
由于初始化單例對象new DCLLazySingleton() 操作并不是原子操作,由于這是很多條指令,jvm可能會亂序執(zhí)行。
在線程1初始化對象可能并未完成,但是此時已經(jīng)instance對象已經(jīng)不為null。(已經(jīng)分配了內(nèi)存,但是構(gòu)造方法還未執(zhí)行完【可能有一些屬性的賦值未執(zhí)行】)
此時線程2再獲取instance 則不為null 直接返回。那么此時線程2獲取的則為‘構(gòu)造方法未執(zhí)行完的instance對象’。則不能保證線程安全。
解決方式:
加上volatile關(guān)鍵字,volatile保證內(nèi)存可見性,內(nèi)存屏障,防止指令排!
加上volatile關(guān)鍵字后,線程2獲取的構(gòu)造方法未執(zhí)行完的instance對象,會在線程1修改之后同步到線程2(volatile 內(nèi)存空間)。所以解決了線程安全問題
參考:
DCL失效原因和解決方案
java 中單例模式DCL的缺陷及單例的正確寫法
4.懶漢式(靜態(tài)內(nèi)部類)public class StaticSingleton { //私有構(gòu)造器 private StaticSingleton() { System.out.println("create StaticSingleton!"); } //獲取單例的方法 public static StaticSingleton getInstance() { return SingletonHolder.instance; } //靜態(tài)內(nèi)部類 持有單例 作為靜態(tài)屬性。 //由于只有在訪問屬性時才會加載靜態(tài)類初始化instance。所以實現(xiàn)了懶加載。且由于JVM保證了類的加載為線程安全,所以為線程安全的。 private static class SingletonHolder { //私有單例屬性 private static StaticSingleton instance = new StaticSingleton(); } }
注意:
由于StaticSingleton類被加載時,內(nèi)部的私有靜態(tài)類SingletonHolder并不會被加載,所以并不會初始化單例instance,當(dāng)getInstance()被調(diào)用時SingletonHolder.instance 才會加載SingletonHolder,由于JVM保證了類的加載為線程安全,因此線程安全。
此方式既可以做到延時加載,也不會因為同步關(guān)鍵字影響性能。是一種比較完善的實現(xiàn)。推薦使用
5.枚舉單例public enum EnumSingleton { INSTANCE(); EnumSingleton() { System.out.println("create EnumSingleton"); } }
線程安全,且能夠抵御反射與序列化。
推薦使用
例外情況上述的單例實現(xiàn)方式還是會面臨一些特殊情況不能保證唯一實例:
反射調(diào)用私有構(gòu)造方法。
序列化后反序列化會生成多個對象??梢詫崿F(xiàn)私有readResolve方法。readObject()如同虛設(shè),直接使用readResolve替換原本返回值。如下:
private Object readResolve () { //返回當(dāng)前對象 return instance; }
由于上述兩情況比較特殊,所以沒有特別關(guān)注。
參考書籍《Java程序性能優(yōu)化》 -葛一鳴 等編著
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/75921.html
摘要:如果需要防范這種攻擊,請修改構(gòu)造函數(shù),使其在被要求創(chuàng)建第二個實例時拋出異常。單例模式與單一職責(zé)原則有沖突。源碼地址參考文獻(xiàn)設(shè)計模式之禪 定義 單例模式是一個比較簡單的模式,其定義如下: 保證一個類僅有一個實例,并提供一個訪問它的全局訪問點。 或者 Ensure a class has only one instance, and provide a global point of ac...
摘要:所以,在版本前,雙重檢查鎖形式的單例模式是無法保證線程安全的。 單例模式可能是代碼最少的模式了,但是少不一定意味著簡單,想要用好、用對單例模式,還真得費一番腦筋。本文對Java中常見的單例模式寫法做了一個總結(jié),如有錯漏之處,懇請讀者指正。 餓漢法 顧名思義,餓漢法就是在第一次引用該類的時候就創(chuàng)建對象實例,而不管實際是否需要創(chuàng)建。代碼如下: public class Singleton...
摘要:總結(jié)單例是運用頻率很高的模式,因為客戶端沒有高并發(fā)的情況,選擇哪種方式并不會有太大的影響,出于效率考慮,推薦使用和靜態(tài)內(nèi)部類實現(xiàn)單例模式。 單例模式介紹 單例模式是應(yīng)用最廣的模式之一,也可能是很多人唯一會使用的設(shè)計模式。在應(yīng)用單例模式時,單例對象的類必須保證只用一個實例存在。許多時候整個系統(tǒng)只需要一個全局對象,這樣有利于我么能協(xié)調(diào)整個系統(tǒng)整體的行為。 單例模式的使用場景 確保某個類有且...
摘要:單例模式可以避免對資源的多重重用。單例模式可以在系統(tǒng)中設(shè)置全局的訪問點,優(yōu)化和共享資源訪問。一個簡單的單例模式場景運行結(jié)果一個管理多個單例的數(shù)組場景運行結(jié)果 單例模式的優(yōu)缺點: 1 單例模式只能在內(nèi)存中存在一個實例,減少了內(nèi)存開支,特別是對一個對象需要頻繁的創(chuàng)建和銷毀時,而且創(chuàng)建和銷毀又不能進(jìn)行優(yōu)化時,單例模式的優(yōu)勢就非常明顯。 2 由于單例只生成一個實例,減少了系統(tǒng)的性能開銷,當(dāng)一...
閱讀 3254·2021-11-19 09:40
閱讀 3034·2021-09-09 09:32
閱讀 820·2021-09-02 09:55
閱讀 1420·2019-08-26 13:23
閱讀 2466·2019-08-26 11:46
閱讀 1256·2019-08-26 10:19
閱讀 2099·2019-08-23 16:53
閱讀 1104·2019-08-23 12:44