單例模式是最廣泛使用的創建模式之一。在現實世界之中,諸如Databae
的連接或者是企業信息系統(EIS)等,通常其創建都是受到限制的,應該儘量複用已存在對象而不是頻繁創建銷燬。爲了達到這個目的,開發者通常會通過實現單例模式來創建一個wrapper類,來封裝資源,限制其運行時所創建對象的個數。
單例中的線程安全
總的來說,開發者一般會按照如下的方式來創建單例的類:
- 使用私有構造函數來避免其它外部引用通過new的方式來創建新的對象引用。
- 聲明一個該類的私有靜態變量爲實例。
- 提供一個公有的靜態方法來返回單例的實例。如果實例還沒有初始化的話,就將其初始化後再返回實例。
通過上面的步驟,我寫了一個如下的單例類:
package com.sapphire.designpatterns;
public class ASingleton {
private static ASingleton instance = null;
private ASingleton() {
}
public static ASingleton getInstance() {
if (instance == null) {
instance = new ASingleton();
}
return instance;
}
}
在上面的代碼中,getInstance()
方法不是線程安全的。多線程可以在同一時間訪問這個方法,而在最開始的少數線程中,實例沒有初始化的時候,多線程可以進入到if代碼塊來創建多個實例,就破壞了單例模式。
通常來說,有三種方式來讓我們保證單例模式的線程安全。
在class加載的時候就創建實例變量
這種實現方式有如下優點:
- 不需要同步即可實現線程安全
- 容易實現
但是也有一些缺點:
- 過早的創建了資源,但是應用可能並不會使用這個資源,造成了資源浪費
- 調用方式無法傳入任何參數的,所以我們無法複用這個類。舉例來說,如果我們希望有一個單例類能夠處理所有的數據庫連接信息,並希望能夠傳入一些數據庫信息的話,我們就無法複用這個單例類了。
同步getInstance()
方法
這種方法有如下優點:
- 保證了線程安全
- 調用方可以傳入任何參數
- 可以保證延遲初始化
當然這種方法也有一些缺點:
- 因爲使用了同步方法,會鎖定資源,令所有客戶端的請求會優先請求鎖,從而降低了性能
- 包含了很多非必要的同步,因爲一旦實例創建完成,同步就只是浪費了性能而沒有任何作用了
在if代碼塊中使用同步代碼塊
優點:
- 保證了線程的安全
- 調用方可以傳遞參數
- 保證了延遲初始化
- 最小化了同步負載,僅僅在最開始的幾個線程請求的時候進行同步操作
淡然也有一定的缺點
- 需要額外的if條件判斷
從三種方法來判斷,第三種方式應該是最佳的方式來實現同步了,代碼大體如下:
package com.sapphire.designpatterns;
public class ASingleton{
private static ASingleton instance= null;
private static Object mutex= new Object();
private ASingleton(){
}
public static ASingleton getInstance(){
if(instance==null){
synchronized (mutex){
if(instance==null) instance= new ASingleton();
}
}
return instance;
}
}
注意,
String
對象非常不適合來作爲同步的鎖,因爲String
可能是複用的對象,鎖定String很容易產生死鎖切很難發現,所以這裏使用的是Object
對象來作爲同步鎖。