為什麼使用列舉法實現單例模式越來越流行

2022-08-13 04:12:15 字數 2781 閱讀 7499

顧名思義,餓漢法就是在第一次引用該類的時候就建立物件例項,而不管實際是否需要建立。**如下:

// 單例的餓漢模式

public class singleton

public static getsignleton()

}

顧名思義,懶漢法就是在需要使用物件例項時才會去建立,單例的懶漢模式(單執行緒)由私有構造器和乙個公有靜態工廠方法構成,在工廠方法中對 singleton 進行 null 判斷,如果是 null 就 new 乙個出來,最後返回 singleton 物件。

這種方法可以實現延時載入,但是有乙個致命弱點:執行緒不安全。如果有兩條執行緒同時呼叫 getsingleton() 方法,就有很大可能導致重複建立物件。

// 單例的懶漢模式(單執行緒)

public class singleton

public static singleton getsingleton()

}

這種寫法考慮了執行緒安全,將對 singleton 的 null 判斷以及 new 的部分使用 synchronized 進行加鎖。同時,對 singleton 物件使用 volatile 關鍵字進行限制,保證其對所有執行緒的可見性,並且禁止對其進行指令重排序優化。如此即可從語義上保證這種單例模式寫法是執行緒安全的。注意,這裡說的是語義上,實際使用中還是存在小坑的

// 單例的懶漢模式(多執行緒)

public class singleton

public static singleton getsingleton()

}return singleton;

}}

雖然上面這種寫法是可以正確執行的,但是其效率低下,還是無法實際應用。因為每次呼叫 getsingleton() 方法,都必須在 synchronized 這裡進行排隊,而真正遇到需要 new 的情況是非常少的,這種寫法被稱為 「雙重檢查鎖」,顧名思義,就是在 getsingleton() 方法中,進行兩次 null 檢查。看似多此一舉,但實際上卻極大提公升了併發度,進而提公升了效能。為什麼可以提高併發度呢?就像上文說的,在單例中 new 的情況非常少,絕大多數都是可以並行的讀操作。因此在加鎖前多進行一次 null 檢查就可以減少絕大多數的加鎖操作,執行效率提高的目的也就達到了。

那麼,這種寫法是不是絕對安全呢?前面說了,從語義角度來看,並沒有什麼問題。但是其實還是有坑。說這個坑之前我們要先來看看 volatile 這個關鍵字。

其實這個關鍵字有兩層語義。第一層語義相信大家都比較熟悉,就是可見性。可見性指的是在乙個執行緒中對該變數的修改會馬上由工作記憶體(work memory)寫回主記憶體(main memory),所以會馬上反應在其它執行緒的讀取操作中。

順便一提,工作記憶體和主記憶體可以近似理解為實際電腦中的快取記憶體和主存,工作記憶體是執行緒獨享的,主存是執行緒共享的。volatile 的第二層語義是禁止指令重排序優化。大家知道我們寫的**(尤其是多執行緒**),由於編譯器優化,在實際執行的時候可能與我們編寫的順序不同。

編譯器只保證程式執行結果與源**相同,卻不保證實際指令的順序與源**相同。這在單執行緒看起來沒什麼問題,然而一旦引入多執行緒,這種亂序就可能導致嚴重問題。volatile 關鍵字就可以從語義上解決這個問題。

注意,前面反覆提到 「從語義上講是沒有問題的」,但是很不幸,禁止指令重排優化這條語義直到 jdk1.5 以後才能正確工作。此前的 jdk 中即使將變數宣告為 volatile 也無法完全避免重排序所導致的問題。所以,在 jdk1.5 版本前,雙重檢查鎖形式的單例模式是無法保證執行緒安全的。

// 單例的懶漢模式(雙重加鎖機制)

public class singleton

public static singleton getsingleton()}}

return singleton;

}}

我們可以把 singleton 例項放到乙個靜態內部類中,這樣就避免了靜態例項在 singleton 類載入的時候就建立物件,並且由於靜態內部類只會被載入一次,所以這種寫法也是執行緒安全的:

// 靜態內部類

public class singleton

private singleton(){}

public static singleton getsingleton()

}

上面提到的所有實現方式都有兩個共同的缺點:

都需要額外的工作 (serializable、transient、readresolve()) 來實現序列化,否則每次反序列化乙個序列化的物件例項時都會建立乙個新的例項。

可能會有人使用反射強行呼叫我們的私有構造器(如果要避免這種情況,可以修改構造器,讓它在建立第二個例項的時候拋異常)。

還有一種更加優雅的方法來實現單例模式,那就是列舉寫法:

// 列舉寫法

public enum singleton

public void setname(string name)

}

// 測試列舉法是否能實現單例模式

public class person

//定義乙個靜態列舉類

static enum singletonenum

public person getinstnce()

}//對外暴露乙個獲取person物件的靜態方法

public static person getinstance()

public static void main(string args)

}

參考資料:

使用列舉enum實現單例模式

單例模式的實現 1,普通模式 public class singledemo public singledemo getinstance return instance 以上模式為懶漢模式 可以改造為飢餓模式,即class載入即new物件 public class singledemo public...

列舉實現單例模式

已經有了雙重判斷加鎖的單例模式,為什麼還要用列舉實現單例模式?列舉的單例模式可以避免反射破壞封裝 先來觀察反射實現的單例 public class reflectdemo 列舉實現單例 這是發現通過列舉可以實現單例模式 若乙個類宣告為列舉類就預設這個類繼承了enum,同時繼承了父類的構造方法。若此時...

為什麼使用單例?

靜態類缺乏可擴充套件性,而普通類能夠方便的重寫某些函式從而對類進行定製。從servlet部分開始,框架將處理請求的許可權交給程式設計師。如果框架傳給程式設計師的是單例,那麼程式設計師自己可以很容易的實現多例 而如果框架傳給程式設計師的是多例,那麼程式設計師自己在不需要多例的時候也只得被迫使用多例。單...