Singleton 模式


Singleton 的英文意義是獨身,也就是只有一個人,應用在物件導向語言上,通常翻譯作單例:單一個實例(Instance)。Singleton 模式可以保證一個類別只有一個實例,並提供一個訪問(visit)這個實例的方法。

一個Singleton實作即為Java中的java.lang.Runtime類別,每個Java程式執行時都有一個唯一的Runtime物件,可以透 過它提供的靜態方法getRuntime()方法來取得這個物件,例如:
Runtime runtime = Runtime.getRuntime();

取得Runtime物件之後,您可以透過它進行一些外部命令的執行、進行垃圾處理等等指令,您可以開啟Runtime.java類別,開頭的幾行是這樣寫 的:
public class Runtime {
    private static Runtime currentRuntime = new Runtime();

    public static Runtime getRuntime() {
        return currentRuntime;
    }

   /** Don't let anyone else instantiate this class */
   private Runtime() {}

    // 以下略
}
 
上面結構即採用Singleton模式設計,其結構使用 UML 來表即如下所示:
Singleton


如上所示的,Java使用 靜態工廠 來取得Runtime物件,其中Runtime的建構式被宣告為private, 這樣可以阻止其他人使用建構方法來建立實例;使用更一般化的表示單例的UML結構,如下圖所示:
Singleton


有幾個實作上面結構的方法,可以在第一次需要實例時再建立物件,也就是採用所謂的Lazy Initialization:
public class Singleton {
    private static Singleton instance = null;

    private Singleton() {
        // ....
    }

    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }

        return instance;
    }

    // .. 其它實作
}

上面的實作適用於單執行緒的程式,在多執行緒的程式下,以上的寫法在多個執行緒的競爭資源下,將仍有可能產生兩個以上的實例,例如下面的情況:
Thread1: if(instance == null) // true
Thread2: if(instance == null) // true

Thread1: instance = new Singleton(); // 產生一個實例
Thread2: instance = new Singleton(); // 又產生一個實例

Thread1: return instance; // 回傳一個實例
Thread2: return instance; // 又回傳一個實例

在多執行緒的環境下,為了避免資源同時競爭而導致如上產生多個實例的情況,加上同步(synchronized)機制:
public class Singleton {
    private static Singleton instance = null;
    private Singleton(){}
    synchronized static public Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

不過這種簡單的寫法不適合用於像伺服器這種服務很多執行緒的程式上,同步機制會造成相當的效能低落,為了顧及Singleton、Lazy Initialization與效能問題,因而有了Double-check Locking的模式:
public class Singleton {
    private static Singleton instance = null;
    private Singleton(){}
    public static Singleton getInstance() {
        if (instance == null){
            synchronized(Singleton.class){
                if(instance == null) {
                     instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

也就是只有在第一次建立實例時才會進入同步區,之後由於實例已建立,也就不用進入同步區進行鎖定。Java中Runtime類別的作法簡單的多,它捨棄了 Lazy Initialization,如果您要取得單例的機會不是很多,可以用這種方式:
public class Singleton {
    private static Singleton instance = new Singleton();

    private Singleton() {
        // ....
    }

    public static Singleton getInstance() {
        return instance;
    }

    // 其它實作
}
 
Singleton本身的觀念簡單但應用很廣,因而很多時候必須對實際環境作一些考量與調整,建議您也看看有關於Singleton的這篇 討 論

如果是Python的話,由於Python是個直譯式語言,也沒有private修飾,所以沒有編譯時期檢查,實作Singleton的方式之一,就是想 辦法於執行時期阻止單一類別實例化兩次,一個例子可能像是:
class Singleton:
__single = None
def __init__(self):
if Singleton.__single:
raise Singleton.__single
Singleton.__single = self
def getSingleton():
if not Singleton.__single:
Singleton.__single = Singleton()
return Singleton.__single
def doSomething(self):
print("do something...XD")

singleton = Singleton.getSingleton()
singleton.doSomething()

 如果Singleton.__single已經存在,再次實例化並執行__init__初始函式時,將會導致直譯失敗(您也許想更正式地繼承 BaseException來引發一個真正的例外)。不過,這個程式基本上還是可以有這樣的實例方式:
x = Singleton() # 雖然只能作一次

如果您真的在乎這個不一致性,可以思考一下,其實類別名稱只是個名稱空間,由於Python是動態語言,真正的型態資訊是在物件上,基本上您回的物件只要 有符合的公開定義即可,所以您可以稍微修改一下:
class Singleton:
__single = None
class __OnlyOne:
def doSomething(self):
print("do something...XD")

def __init__(self):
raise Singleton.__single

def getSingleton():
if not Singleton.__single:
Singleton.__single = Singleton.__OnlyOne()
return Singleton.__single

singleton = Singleton.getSingleton()
singleton.doSomething()

這麼一來,若試圖實例化Singleton,就一定會直譯失敗,您也可以進一步利用Python的特性,定義__new__()方法來達到需求:
class Singleton:
__single = None
def __new__(clz):
if not Singleton.__single:
Singleton.__single = object.__new__(clz)
return Singleton.__single

def doSomething(self):
print("do something...XD")

singleton = Singleton()
singleton.doSomething()

在上例中,一旦已建立了實例,則之後__new__()傳回的都是同一個實例。