單例設計模式是一種軟件設計模式,它將類的實例化限制為一個對象。與其他創造性設計模式(如抽象工廠)相比,單例構建器模式將創建一個對象,並且還將負責只存在該對象的一個實例。
當創建一個單例類時,有一些問題需要記住:
如何確保一個類只有一個實例?
如何方便地訪問類的惟一實例?
類如何控制實例化?
如何限制類的實例數量?
例如,假設我們有一個發送消息的類 — 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的讀取對任何後續的讀寫進行重新排序。另外,互斥對象用於實現同步。
總之,我們創建了一個對象,並確保該對象只有一個實例。我們確保在多線程環境中實例化對象不會出現任何問題。
閱讀更多 程序你好 的文章