如何構建創造性設計模式:單例模式

單例設計模式是一種軟件設計模式,它將類的實例化限制為一個對象。與其他創造性設計模式(如抽象工廠)相比,單例構建器模式將創建一個對象,並且還將負責只存在該對象的一個實例。

當創建一個單例類時,有一些問題需要記住:

如何確保一個類只有一個實例?

如何方便地訪問類的惟一實例?

類如何控制實例化?

如何限制類的實例數量?

例如,假設我們有一個發送消息的類 — the Messenger class:

package com.gkatzioura.design.creational.singleton;

public class Messenger {

public void send(String message) {

}

}

但是,我們希望消息過程僅由Messenger類的一個實例來處理。讓我們假設一個場景:Messenger類打開一個tcp連接(例如,XMPP),並且為了發送消息,必須保持連接的存在。每次必須發送消息時,打開新的XMPP連接會非常低效。

因此,我們將繼續並使Messenger類成為單例。

package com.gkatzioura.design.creational.singleton;

public class Messenger {

private static Messenger messenger = new Messenger();

private Messenger() {}

public static Messenger getInstance() {

return messenger;

}

public void send(String message) {

}

}

如您所見,我們將messenger構造函數設置為private,並使用靜態變量初始化一個messenger。靜態變量是類級變量,其中內存分配只在類被加載到內存中時發生一次。在此過程中,我們確保Messenger類將只實例化一次。getInstance方法將在調用靜態messenger實例時獲取它。

顯然,前面的方法有其優點和缺點,我們不必擔心線程安全性,並且只有在加載Messenger類時才會創建實例。但是,它缺乏靈活性。讓我們考慮將配置變量傳遞給Messenger構造函數的場景。您將發現使用以前的方法是不可能的。

解決方案是在getInstance方法上實例化Messenger類。

package com.gkatzioura.design.creational.singleton.lait;

public class Messenger {

private static Messenger messenger;

private Messenger() {}

public static Messenger getInstance() {

if(messenger==null) {

messenger = new Messenger();

}

return messenger;

}

public void send(String message) {

}

}

上述方法在某些情況下可能有效,但在多線程環境中實例化類的情況下,它會忽略線程安全性。

使類線程安全的最簡單方法是同步getInstance方法。

package com.gkatzioura.design.creational.singleton.lait;

public class Messenger {

private static Messenger messenger;

private Messenger() {}

public synchronized static Messenger getInstance() {

if(messenger==null) {

messenger = new Messenger();

}

return messenger;

}

public void send(String message) {

}

}

上面的代碼可以正常工作。至少,messenger的創建將是同步的,不會創建重複的副本。這種方法的問題是,只有在創建對象時才需要同步。使用上述代碼將導致不必要的開銷。

另一種方法是使用雙重檢查鎖定方法。現在,雙重檢查鎖定需要特別小心,因為很容易在錯誤的實現中選擇正確的實現。最好的方法是使用volatile關鍵字實現延遲加載。

package com.gkatzioura.design.creational.singleton.dcl;

public class Messenger {

private static final Object lock = new Object();

private static volatile Messenger messenger;

private Messenger() {}

public static Messenger getInstance() {

if(messenger==null) {

synchronized (lock) {

if(messenger==null) {

messenger = new Messenger();

}

}

}

return messenger;

}

public void send(String message) {

}

}

通過使用volatile關鍵字,我們可以防止volatile對象的寫入對任何先前的讀寫進行重新排序,防止volatile的讀取對任何後續的讀寫進行重新排序。另外,互斥對象用於實現同步。

總之,我們創建了一個對象,並確保該對象只有一個實例。我們確保在多線程環境中實例化對象不會出現任何問題。


分享到:


相關文章: