_lowbee瞎搞
單例模式
一、什麼是單例模式
單例模式指的是在應用整個生命週期內只能存在一個例項。單例模式是一種被廣泛使用的設計模式。他有很多好處,能夠避免例項物件的重複建立,減少建立例項的系統開銷,節省記憶體。
二、單例模式和靜態類的區別
首先理解一下什麼是靜態類,靜態類就是一個類裡面都是靜態方法和靜態field,構造器被private修飾,因此不能被例項化。Math類就是一個靜態類。
知道了什麼是靜態類後,來說一下他們兩者之間的區別:
- 首先單例模式會提供給你一個全域性唯一的物件,靜態類只是提供給你很多靜態方法,這些方法不用建立物件,通過類就可以直接呼叫;
- 單例模式的靈活性更高,方法可以被override,因為靜態類都是靜態方法,所以不能被override;
- 如果是一個非常重的物件,單例模式可以懶載入,靜態類就無法做到;
- 那麼時候時候應該用靜態類,什麼時候應該用單例模式呢?首先如果你只是想使用一些工具方法,那麼最好用靜態類,靜態類比單例類更快,因為靜態的繫結是在編譯期進行的。如果你要維護狀態資訊,或者訪問資源時,應該選用單例模式。還可以這樣說,當你需要面向物件的能力時(比如繼承、多型)時,選用單例類,當你僅僅是提供一些方法時選用靜態類。
三、實現單例模式
這裡先放一下測試主程式
每個物件的hashCode相同,即每個物件相同;hashCode不同,即建立了多個物件,每個物件不同
class MyThread implements Runnable { @Override public void run() { Thread currentThread = Thread.currentThread(); // 餓漢模式 執行緒安全 //System.out.println(currentThread.getName() + SingletonHungry.getInstance().hashCode()); // 懶漢模式 執行緒不安全 //System.out.println(currentThread.getName() + SingletonLazy.getInstance().hashCode()); // 懶漢模式 同步方法 執行緒安全 //System.out.println(currentThread.getName() + SingletonLazySyncMethod.getInstance().hashCode()); // 懶漢模式 同步程式碼塊 執行緒安全 //System.out.println(currentThread.getName() + SingletonLazySyncBlock.getInstance().hashCode()); // 懶漢模式 縮小同步程式碼塊 執行緒不安全 //System.out.println(currentThread.getName() + SingletonLazySyncSmallBlock.getInstance().hashCode()); // 懶漢模式 雙重鎖機制 執行緒安全 //System.out.println(currentThread.getName() + SingletonLazyDoubleCheck.getInstance().hashCode()); } } public class SingletonPatternTest { public static void main(String[] args) { // 開啟10個執行緒 for (int i = 0; i < 10; i++) { MyThread myThread = new MyThread(); // 開啟執行緒 new Thread(myThread, "執行緒" + i + ":").start(); } } }
1、餓漢模式
所謂餓漢模式就是立即載入,一般情況下再呼叫getInstance方法之前就已經產生了例項,也就是在類載入的時候已經產生了。這種模式的缺點很明顯,就是佔用資源,當單例類很大的時候,其實我們是想使用的時候再產生例項。因此這種方式適合佔用資源少,在初始化的時候就會被用到的類。
// 餓漢模式 執行緒安全 public class SingletonHungry { private static SingletonHungry singletonHungry = new SingletonHungry(); // 私有化構造方法 private SingletonHungry() {} // 提供返回該物件的靜態方法 public static SingletonHungry getInstance() { return singletonHungry; } }
測試結果
2、懶漢模式1
懶漢模式就是延遲載入,也叫懶載入。在程式需要用到的時候再建立例項,這樣保證了記憶體不會被浪費。針對懶漢模式,這裡給出了幾種實現方式,但有些實現方式是執行緒不安全的,也就是說在多執行緒併發的環境下可能出現資源同步問題。
// 懶漢模式 執行緒不安全
public class SingletonLazy {
private static SingletonLazy singletonLazy = null;
private SingletonLazy() {}
public static SingletonLazy getInstance() {
if (null == singletonLazy) {
try {
// 模擬物件建立之前的準備工作
Thread.sleep(1000);
singletonLazy = new SingletonLazy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return singletonLazy;
}
}
測試結果
3、懶漢模式2
因為上述懶漢模式1是執行緒不安全的,我們可以考慮用synchronized修飾getInstance方法進行同步實現執行緒安全
// 懶漢模式 執行緒安全
public class SingletonLazySyncMethod {
private static SingletonLazySyncMethod singletonLazySyncMethod = null;
private SingletonLazySyncMethod() {}
public static synchronized SingletonLazySyncMethod getInstance() {
if (null == singletonLazySyncMethod) {
try {
// 模擬物件建立之前的準備工作
Thread.sleep(1000);
singletonLazySyncMethod = new SingletonLazySyncMethod();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return singletonLazySyncMethod;
}
}
測試結果
這種方法雖然實現了執行緒安全,但是由於是同步執行的,下個執行緒想要取得物件,就必須要等到上一個執行緒釋放,才可以繼續執行,所以效率很低
4、懶漢模式3
上述懶漢模式2效率太低,我們可以將它稍微改進,不用同步方法實現執行緒安全,可以在方法內部採用同步程式碼塊實現執行緒安全,像這樣
// 懶漢模式 執行緒安全
public class SingletonLazySyncBlock {
private static SingletonLazySyncBlock singletonLazySyncBlock = null;
private SingletonLazySyncBlock() {}
public static SingletonLazySyncBlock getInstance() {
synchronized (SingletonLazySyncBlock.class) {
if (null == singletonLazySyncBlock) {
try {
// 模擬物件建立之前的準備工作
Thread.sleep(1000);
singletonLazySyncBlock = new SingletonLazySyncBlock();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return singletonLazySyncBlock;
}
}
}
測試結果
這種方式雖然也實現了執行緒安全,但是效率還是很低
5、懶漢模式4
上述懶漢模式3使用同步程式碼塊實現了執行緒安全,但是效率還是較為低下。接下來。我們試著再次縮小同步程式碼塊的範圍
// 懶漢模式 執行緒不安全
public class SingletonLazySyncSmallBlock {
private static SingletonLazySyncSmallBlock singletonLazySyncSmallBlock = null;
private SingletonLazySyncSmallBlock() {}
public static SingletonLazySyncSmallBlock getInstance() {
if (null == singletonLazySyncSmallBlock) {
try {
// 模擬物件建立之前的準備工作
Thread.sleep(1000);
synchronized (SingletonLazySyncSmallBlock.class) {
singletonLazySyncSmallBlock = new SingletonLazySyncSmallBlock();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return singletonLazySyncSmallBlock;
}
}
測試結果
上述測試結果,我們可以看到,很不辛失敗了,縮小了同步程式碼塊的範圍不能保證執行緒安全了。分析一下原因:我們假設有兩個執行緒A、B同時走進了if程式碼塊中,由於執行緒A比較牛批,首先搶到了鎖,就去建立物件了;而執行緒B只能等待,執行緒A建立完成物件之後,釋放鎖執行緒B拿到鎖也會去建立物件,這樣就建立了兩個物件,所以多執行緒環境下就不能保證單例了。
6、懶漢模式5
上述懶漢模式4,為什麼出現執行緒不安全,我們已經分析過了。接下來我們考慮如何去解決執行緒不安全。還拿上述執行緒A、B的例子來說,執行緒B之所以會再次建立物件,是因為執行緒B已經闖過了if條件的判斷,那麼我們此時考慮線上程B拿到鎖之前,再讓執行緒B去進行一次if判斷,也就是在synchronized程式碼塊之前再加一層if判斷,好的,問題解決。這樣,既縮小了同步程式碼塊的範圍,又保證了多執行緒環境下的執行緒安全。而且這種條件下,不像上述的懶漢模式2和懶漢模式3,懶漢模式2的同步方法是每次都要執行的,懶漢模式3的同步程式碼塊也是每次都要執行的,而這裡的懶漢模式5同步程式碼塊的執行次數與第一次同時闖過第一層if判斷的執行緒數相同,而後續執行緒過第一層if判斷時,在第一層if判斷就被攔截了。所以大大提高了程式的效率。這種方式就叫做雙重檢查鎖機制
// 懶漢模式 執行緒安全
public class SingletonLazyDoubleCheck {
// volatile修飾
private volatile static SingletonLazyDoubleCheck singletonLazyDoubleCheck = null;
private SingletonLazyDoubleCheck() {}
public static SingletonLazyDoubleCheck getInstance() {
if (null == singletonLazyDoubleCheck) {
// 模擬物件建立之前的準備工作
try {
Thread.sleep(1000);
synchronized (SingletonLazyDoubleCheck.class) {
if(null == singletonLazyDoubleCheck) {
singletonLazyDoubleCheck = new SingletonLazyDoubleCheck();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return singletonLazyDoubleCheck;
}
}
測試結果