參考 那些年,我們一起寫過的“單例模式”
這篇文章對單例模式講的非常清楚,從如何使用講到如何選擇,討論了各種會導致單例失效的情況及如何避免。
這里僅做一些筆記,學習還是要點開上面這篇
- 單例的用法
- 如何選擇
幾種常用用法
-
懶漢式單例
第一次調用時實例化,
可以利用 synchronized 修飾 getST () 實現同步,但速度慢
延遲加載
public class SingletonTest {
private static SingletonTest ST;
private SingletonTest() {}
public static SingletonTest getST () {
if (ST == null) {
synchronized (SingletonTest.class) {
ST = new SingletonTest();
}
}
return ST;
}
}
-
雙重檢查鎖定( Double-Checked Locking「DCL」)
優化后的懶漢,這樣既同步代碼塊保證了線程安全,同時實例化的代碼也只會執行一次,實例化后同步操作不會再被執行,從而效率提升很多
但在 JDK 版本小于 1.5 時會有 DCL 失效的問題
關于 Double-Checked Locking 還有一種說法,在 java 是不安全的,見于 The "Double-Checked Locking is Broken" Declaration
public class SingletonTest{
private static volatile SingletonTest ST;
private SingletonTest() {//初始化過程}
public static SingletonTest getSingletonTest(){
//第一次判斷避免進行不必要的同步操作
if(ST == null){
synchronized(SingletonTest.class){
//第二次判斷保證線程的安全
if (ST == null) {
ST = new SingletonTest();
}
}
}
return ST;
}
}
-
靜態內部類單例
利用了 classloder 的機制來保證初始化 INSTANCE 時只會有一個
延遲加載
public class SingletonTest{
private SingletonTest() {//初始化過程}
public static SingletonTest getSingletonTest(){
return SingletonTestHolder.INSTANCE;
}
private static class SingletonTestHolder{
private static final SingletonTest INSTANCE = new SingletonTest();
}
}
-
餓漢式單例
初始化類的時候進行實例化,線程安全
非延時加載,急切加載(餓加載)
如果實例創建時依賴于某個非靜態方法的結果,或者依賴于配置文件等,就不考慮使用餓漢模式了
public class SingletonTest{
private static final SingletonTest ST = new SingletonTest();
private SingletonTest() {//初始化過程}
public static SingletonTest getSingletonTest(){return ST;}
}
-
枚舉單例
可以防止序列化對單例的破壞(詳見上面的參考文章)
Public enum SingletonTest{
INSTANCE;//一個枚舉的元素代表 SingletonTest 類的一個實例
}
在這個枚舉類中還會有其他的變量和方法,這些和其他的類沒有區別,而我們可以用 SingletonTest.INSTANCE
來使用這些方法,
如何選擇及原因
示例圖
首先要說的是,不要濫用單例模式,要分清楚需求,對于那些不是頻繁創建和銷毀,且創建和銷毀也不會消耗太多資源的情況就不要使用單例。
然后急切加載雖然快但會占用更多的內存,延遲加載反之。
而且這些單例模式也不是一定保證實例的唯一,像反射、序列化、克隆等操作都有可能破壞我們的單例。