博文原址:折騰Java設計模式之單例模式
單例模式
Ensure a class has only one instance, and provide a global point of access to it.
一個類僅僅只有一個例項,並且提供全域性的接入點。簡潔點理解就是涉及到一個單一的類,該類負責建立自己的物件,同時確保只有單個物件被建立。這個類提供了一種訪問它自己唯一的物件的方式,可以直接訪問,不需要例項化該類的物件。
餓漢式單例模式
public final class EagerSingleton {
private static final EagerSingleton INSTANCE = new EagerSingleton();
private EagerSingleton() {
}
public static EagerSingleton getInstance() {
return INSTANCE;
}
}
複製程式碼
基於 classloader 機制避免了多執行緒的同步問題,不過INSTANCE
在類裝載時就例項化,雖然導致類裝載的原因有很多種,在單例模式中大多數都是呼叫 getInstance
方法。類在載入時就初始化了,會浪費空間,因為不管你用還是不用,它都建立出來了,但是因為沒有加鎖,執行效率較高。
懶漢式單例模式
/**
* 懶漢式的單例模式-執行緒不安全的
*/
public final class LazyThreadNotSafeSingleton {
private static LazyThreadNotSafeSingleton INSTANCE;
private LazyThreadNotSafeSingleton() {
}
public static LazyThreadNotSafeSingleton getInstance() {
if (null == INSTANCE) {
INSTANCE = new LazyThreadNotSafeSingleton();
}
return INSTANCE;
}
}
複製程式碼
/**
* 懶漢式的單例模式-執行緒安全的
*/
public final class LazyThreadSafeSingleton {
private static LazyThreadSafeSingleton INSTANCE;
private LazyThreadSafeSingleton() {
}
public static synchronized LazyThreadSafeSingleton getInstance() {
if (null == INSTANCE) {
INSTANCE = new LazyThreadSafeSingleton();
}
return INSTANCE;
}
}
複製程式碼
有上述兩種懶漢式單例模式,區別在與靜態工廠方法getInstance
是否加了synchronized
修飾來進行同步,用於支援執行緒安全。懶漢式,在其載入物件的時候是不會建立物件例項的,只有等它真正使用的時候才會建立,如果一直沒有使用則一直不會建立,能夠避免記憶體浪費,也就是隻有第一次呼叫的時候才會建立。但是加鎖synchronized
就影響了效能和效率,導致getInstance
方法的效能受影響,此種方式也不推薦。尋找一種既能執行緒安全又可以延遲載入的方式。
雙檢查鎖的單例模式
/**
* 雙檢查鎖的單例模式-執行緒安全
*/
public final class DoubleCheckLockingSingleton {
private static volatile DoubleCheckLockingSingleton INSTANCE;
private DoubleCheckLockingSingleton() {
}
public static DoubleCheckLockingSingleton getInstance() {
// 第一次檢查例項是否存在,如果存在即可返回,不存在則進入同步塊
if (null == INSTANCE) {
// 同步塊,執行緒安全
synchronized (DoubleCheckLockingSingleton.class) {
// 第二次檢查例項是否存在,如果還不存在則會真正的建立例項
if (null == INSTANCE) {
INSTANCE = new DoubleCheckLockingSingleton();
}
}
}
return INSTANCE;
}
}
複製程式碼
雙檢查加鎖的方式,能實現執行緒安全,又能減少效能的影響。雙檢查加鎖,旨在每次呼叫getInstance
方法都需要同步,但是先不會同步,在第一次判斷例項是否存在後,如果不存在才進入同步塊,進入同步塊後,第二次檢查例項是否存在,如果不存在,在同步塊內建立例項。如此只有首次才會同步,從而減少了多次在同步情況下進行判斷所浪費的時間。雙檢查加鎖機制的實現會使用關鍵字volatile,它的意思是:被volatile修飾的變數的值,將不會被本地執行緒快取,所有對該變數的讀寫都是直接操作共享記憶體,從而確保多個執行緒能正確的處理該變數。但是實現過程稍微複雜點。
靜態內部類Holder式單例模式
/**
* 靜態內部類Holder式單例
*
* 延遲載入和執行緒安全
*/
public final class LazyInitializationHolderSingleton {
private LazyInitializationHolderSingleton() {
}
public static LazyInitializationHolderSingleton getInstance() {
return InstanceHolder.INSTANCE;
}
/**
* 延遲載入
*/
private static class InstanceHolder {
private static final LazyInitializationHolderSingleton INSTANCE = new LazyInitializationHolderSingleton();
}
}
複製程式碼
當getInstance
方法第一次被呼叫的時候,它第一次讀取InstanceHolder.INSTANCE
,導致InstanceHolder
類得到初始化;而這個類在裝載並被初始化的時候,會初始化它的靜態域,從而建立Singleton的例項,由於是靜態的域,因此只會在虛擬機器裝載類的時候初始化一次,並由虛擬機器來保證它的執行緒安全性。這個模式的優勢在於,getInstance
方法並沒有被同步,並且只是執行一個域的訪問,因此延遲初始化並沒有增加任何訪問成本。其中使用到類的靜態內部類和多執行緒預設同步鎖。
靜態內部類
靜態內部類指,有static修飾的成員式內部類。如果沒有static修飾的成員式內部類被稱為物件級內部類。類級內部類相當於其外部類的static成分,它的物件與外部類物件間不存在依賴關係,因此可直接建立。而物件級內部類的例項,是繫結在外部物件例項中的。靜態內部類中,可以定義靜態的方法。在靜態方法中只能夠引用外部類中的靜態成員方法或者成員變數。靜態內部類相當於其外部類的成員,只有在第一次被使用的時候才被會裝載。
多執行緒預設同步鎖
在多執行緒開發中,為解決併發問題,主要是通過使用synchronized來加互斥鎖進行同步控制。但是在某些情況中,JVM已經隱含地為您執行了同步,這些情況下就不用自己再來進行同步控制了。這些情況包括:
1.由靜態初始化器(在靜態欄位上或static{}塊中的初始化器)初始化資料時;
2.訪問final欄位時;
3.在建立執行緒之前建立物件時;
4.執行緒可以看見它將要處理的物件時
列舉型別的單例模式
/**
* 採用列舉型別的單例模式
*/
public enum SingletonEnum {
INSTANCE;
@Override
public String toString() {
return getDeclaringClass().getCanonicalName() + "@" + hashCode();
}
public void something(){
//do something...
}
}
複製程式碼
簡潔,自動支援序列化機制,絕對防止多次例項化。《高效Java 第二版》中的說法:單元素的列舉型別已經成為實現Singleton的最佳方法。用列舉來實現單例非常簡單,只需要編寫一個包含單個元素的列舉型別即可。
總結
不建議使用懶漢式,簡單的闊以使用餓漢式。涉及到反序列化建立物件時闊以使用列舉方式。如果考慮到延遲載入 的話,闊以採用靜態內部類Holder的模式。如果對業務需求有特殊要求的時候闊以採用雙檢查鎖的單例。
參考
歡迎關注瞭解最新動態更新