【設計模式】Singleton

前言

Singleton設計模式,確保全局只存在一個該類的實例。將構造器聲明爲private,防止調用(雖然仍是可使用反射來調用)。聲明一個靜態的類實例在類中,聲明一個公共的獲取實例的方法。這篇博文給出了簡單的實現方法,分析如何作到線程安全,整理了使用Singleton的壞處。java

線程安全

方法一是線程安全的,在類被裝載的時候,就初始化這個成員,Java庫中Runtime就是用了這個方法。
方法二不是線程安全的。若是多個線程同時進入到函數中(臨界區),那麼會返回多個不一樣的實例。設計模式

代碼驗證安全

如下代碼驗證了線程不安全,即多線程的狀況下方法二不能保證真正的「單例」。經過打印每一個類的hashCode來顯示,類實例不惟一。多線程

class Singleton {
    private static Singleton singleton;

    private Singleton() {}

    public static Singleton getInstance() {
        if (singleton == null) {
            try {
                Thread.sleep(233);
                singleton = new Singleton();
            }
            catch (Exception e) {
                e.printStackTrace();
            }
        }
        return singleton;
    }
}

public class Main {
    public static void main(String args[]) {
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                public void run() {
                    Singleton singleton = Singleton.getInstance();
                    System.out.println(singleton.hashCode());
                }
            }).start();
        }
    }
}

解決方案ide

一種簡單的處理方法是,將getInstance聲明爲synchronized的,效率低,由於每個線程都在等待進入臨界區。函數

另外一種方法叫作Double Checked Locking(DCL),將公共變量聲明爲volatile(每次要使用這個變量的時候,從內存中取出來,保證各個線程能夠看到這個變量的修改)。單元測試

class Singleton {
    private volatile static Singleton singleton;

    private Singleton() {}

    public static Singleton getInstance() {
        if (singleton == null) {
            try {
                Thread.sleep(233);
                synchronized (Singleton.class) {
                    if (singleton == null) {
                        singleton = new Singleton();
                    }
                }
            }
            catch (Exception e) {
                e.printStackTrace();
            }
        }
        return singleton;
    }
}

壞處

有啥壞處,別怪這個設計模式,都是全局變量的錯。測試

1, 全局變量線程

Singleton管理公共的資源,在代碼中的任何地方,只要調用Singleton的獲取實例的方法,就能夠獲取到Singleton的實例,能夠對這些公共資源的讀寫。這讓Singleton成爲了一個全局變量。設計

全局變量的壞處,也是Singleton的壞處。全局變量自有全局變量的使用情景和優勢,要分開兩面看待,這裏只講壞處並不是想說全局變量一無可取。

在分析全局變量的壞處以前,在[2]下面看到了一個特別有意思的比喻:

Using global state with your fellow programmers is like using the same toothbrush with your friends - you can but you never know when someone will decide to shove it up his bum.

我想這位老兄說出這個觀點,大概是用過異味的牙刷吧。爲什麼不用規範來約束一同使用全局變量的人呢?

這位老兄道出了全局變量的本質,任何人能夠用它作任何事,給整個程序帶來了極大的不肯定性。網上關於全局變量的壞處討論不少,下面整理了一些,雖然並不是都很壞:

  1. 代碼耦合程度更高。假設一些函數依賴於全局變量的狀態,這些函數經過這個全局變量聯繫到一塊兒。一個函數的修改對另外一個函數的讀取存在影響,這些函數在無形中聯繫到了一塊兒。
  2. 給測試帶來困難。全局變量存儲了一些狀態,須要安排好模塊的運行順序才能夠正確的測試。但是,單元測試應該是相互獨立的,而非有順序的。
  3. 多線程的寫入的時候,要互斥。
  4. 破壞了函數的輸入輸出功能。拿全局變量來讀輸入,寫輸出。
  5. 命名衝突。
  6. 可讀性下降。要理解一個函數,還須要去跟蹤使用到的全局變量的前因後果。

2, 破壞了單一職責原則

定義:就一個類而言,應該僅有一個引發它變化的緣由

當一個類使用了Singleton的時候,它不只負責這個類須要完成的任務,還負責這個單一對象資源的建立和管理。這個類的函數作兩項任務,相關性低。

這一點算不上太壞。畢竟設計原則並不老是須要遵照的。

正確使用

上面講了壞處,核心要義是避免將Singleton用做全局變量。Singleton的使用場景是什麼呢?

回到Singleton的本質做用上來,只須要一個這個類的實例。前面講這個設計模式破壞了單一職責原則,由於須要管理這個實例。

因此歸結起來使用這個設計模式的兩個緣由:

(1) 單個實例

(2) 實例的管理

在[1]中講了使用Singleton的一個具體例子。那就是日誌(log)。由於log和代碼的實質功能並不會產生耦合,因此是否開啓log對於系統的功能沒有太大的影響。

參考連接

  1. https://stackoverflow.com/questions/228164/on-design-patterns-when-should-i-use-the-singleton
  2. https://softwareengineering.stackexchange.com/questions/148108/why-is-global-state-so-evil
  3. https://stackoverflow.com/questions/26285520/implementing-singleton-with-an-enum-in-java
  4. https://stackoverflow.com/questions/137975/what-is-so-bad-about-singletons?page=1&tab=votes#tab-top
相關文章
相關標籤/搜索