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