Java設計模式之單例模式
定義
單例模式,是一種常用的軟體設計模式。在它的核心結構中只包含一個被稱為單例的特殊類。通過單例模式可以保證系統中一個類只有一個例項。即一個類只有一個物件例項。
特點
- 只能有一個例項
- 必須自己建立唯一例項(外部無法通過new來建立該物件例項)
- 需要給外部物件提供返回該唯一例項的公有方法
設計要點
- 指向自己例項的私有靜態引用
- 私有構造方法
- 以自己例項為返回值的公有方法
型別
單例模式有懶漢模式和餓漢模式兩種
- 懶漢就是在類載入時,內部的靜態例項物件便開始建立。
- 餓漢就是隻有在第一次使用時才會去建立例項物件。
程式碼展示
//餓漢模式(非延遲載入) public class EagerSingleton { private static EagerSingleton eagerSingleton = new EagerSingleton(); private EagerSingleton(){} public static EagerSingleton getInstance(){ return eagerSingleton; } }
//懶漢模式(延遲載入) public class LazySingleton { private static LazySingleton lazySingleton; private LazySingleton(){} //這裡存線上程安全問題,需要同步 public static synchronized LazySingleton getInstance(){ if(lazySingleton == null){ lazySingleton = new LazySingleton(); } return lazySingleton; } }
在懶漢模式中,當不同執行緒都去獲取例項時,會產生執行緒安全問題,可以將getInstance方法進行同步,當併發量較高時,可以使用一種雙重檢查來提高效率,程式碼如下。
public class DoubleCheck_lazySingleton { private static volatile DoubleCheck_lazySingleton instance; private DoubleCheck_lazySingleton(){} private static DoubleCheck_lazySingleton getInstance(){ if(instance == null){ synchronized (DoubleCheck_lazySingleton.class){ if(instance == null){ instance = new DoubleCheck_lazySingleton();//1 } } } return instance; } }
- 第一層判斷是為了當instance被例項化後,避免不必要的同步操作,第二層判斷是為了解決當兩個執行緒都通過了第一個判斷後,此時其中一個執行緒進入同步程式碼塊獲得鎖,例項化Instance,第二個執行緒便會退出判斷並返回。
- 一般來講,當初始化一個物件的時候,會經歷記憶體分配、初始化、返回物件在堆上的引用等一系列操作,這種方式產生的物件是一個完整的物件,可以正常使用。但是JAVA的無序寫入可能會造成順序的顛倒,即記憶體分配、返回物件引用、初始化的順序,這種情況下對應到//1就是instance已經不是null,而是指向了堆上的一個物件,但是該物件卻還沒有完成初始化動作。當後續的執行緒發現singleton不是null而直接使用的時候,就會出現意料之外的問題。
- JDK1.5之後,可以使用volatile關鍵字修飾變數來解決無序寫入產生的問題,因為volatile關鍵字的一個重要作用是禁止指令重排序,即保證不會出現記憶體分配、返回物件引用、初始化這樣的順序,從而使得雙重檢測真正發揮作用。此處就是利用volatile的特點,保證在對instance進行read操作時讀到的一定是最新的完全例項化的物件。
優點
- 該類在記憶體中只有一個物件,節省記憶體空間。
- 避免頻繁的建立銷燬物件,可以提高效能。
- 避免對共享資源的多重佔用。
- 可以全域性訪問。
缺點
- 擴充套件困難,由於getInstance靜態函式無法生成子類的例項。如果要拓展,只有重寫該類。
- 隱式使用引起類結構不清晰。
- 導致程式記憶體洩露的問題。
場景
- 需要頻繁例項化然後銷燬的物件。
- 建立物件時耗時過多或者耗資源過多,但又經常用到的物件。
- 資源共享的情況下,避免由於資源操作時導致的效能或損耗等
- 控制資源的情況下,方便資源之間的互相通訊。
注意事項
- 只能使用單例類提供的方法得到單例物件,不要使用反射,否則將會例項化一個新物件。
- 不要做斷開單例類物件與類中靜態引用的危險操作。
- 多執行緒使用單例使用共享資源時,注意執行緒安全問題。