Java線程和多線程(五)——單例類中的線程安全

單例模式是最廣泛使用的創建模式之一。在現實世界之中,諸如Databae的連接或者是企業信息系統(EIS)等,通常其創建都是受到限制的,應該儘量複用已存在對象而不是頻繁創建銷燬。爲了達到這個目的,開發者通常會通過實現單例模式來創建一個wrapper類,來封裝資源,限制其運行時所創建對象的個數。

單例中的線程安全

總的來說,開發者一般會按照如下的方式來創建單例的類:

  1. 使用私有構造函數來避免其它外部引用通過new的方式來創建新的對象引用。
  2. 聲明一個該類的私有靜態變量爲實例。
  3. 提供一個公有的靜態方法來返回單例的實例。如果實例還沒有初始化的話,就將其初始化後再返回實例。

通過上面的步驟,我寫了一個如下的單例類:

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對象來作爲同步鎖。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章