大衛的Design Patterns學習筆記05:Singleton

一、概述
在很多情況下,我們的系統只允許某個類有一個或指定個數的實例,如一般的應用系統往往有且僅有一個log文件操作類實例,或者,整個系統僅有一個等待事務隊列等(注意:Singleton不是用來解決整個應用程序僅有一個實例這樣的問題的),在這些情況下可以考慮使用Singleton模式。
Singleton(單件)模式用於保證一個類僅有一個實例,並提供一個訪問該實例的全局訪問點。(GoF: Ensure a class only has one instance, and provide a global point of access to it.

二、結構
Singleton典型的結構如下圖所示:

1:Singleton模式的類圖示意

三、應用
Singleton模式從概念上講應該是所有模式中最簡單的,它的目的很簡單:限制我們創建實例的個數,Only one is permited。不過,該模式也可以擴展到允許指定數量個實例的情況。
一眼看去,Singleton似乎有些像全局對象。但是實際上,並不能用全局對象代替Singleton模式,這是因爲:其一,有些編程語言例如Java、C#等,根本就不支持全局變量。其二,全局對象的方法並不能阻止人們將一個類實例化多次:除了類的全局實例外,開發人員仍然可以通過類的構造函數創建類的多個局部實例。而Singleton模式則通過從根本上控制類的創建,將“保證只有一個實例”這個任務交給了類本身,開發人員不可能再有其它途徑得到類的多個實例。這一點是全局對象方法與Singleton模式的根本區別。

四、優缺點
Singleton模式是作爲"全局變量"的替代品出現的,所以它具有全局變量的特點:全局可見;它也具有全局變量不具備的性質:同類型的對象實例只可能有一個。對於全局變量“貫穿應用程序的整個生命期”的特性,對於Singleton,視具體的實現方法,並不一定成立。

五、舉例
實現該模式最簡便的方法就是:採用static變量,並將類的構造函數、拷貝構造函數、賦值函數聲明爲private或者protected,同時提供public的實例創建/訪問函數。
示例代碼如下:

#include <assert.h>
#include <assert.h>
#include <iostream>
using namespace std;

class
 Singleton
{

public
:
    static
 Singleton* Instance() {
        if
 (pInstance == NULL)
            pInstance = new Singleton();
        return
 pInstance;
    }

protected
:
    Singleton() {}
private
:
    static
 Singleton* pInstance;
    Singleton(const Singleton&) ;
    Singleton& operator=(const Singleton&) ;
};


Singleton* Singleton::pInstance = NULL;

int
 main()
{

    //Singleton instance;                    // Error
    //Singleton* pInstance = new Singleton;    // Error
    //Singleton instance = Singleton::Instance();    // Error
    Singleton* pInstance1 = Singleton::Instance(); // Good
    Singleton* pInstance2 = Singleton::Instance();

    cout.setf(ios::hex, ios::basefield);
    cout << "pInstance1 = " << pInstance1 << endl;
    cout << "pInstance2 = " << pInstance2 << endl;
    cout.setf(ios::dec, ios::basefield);
    assert(pInstance1 == pInstance2);
    
    return
 0;
}


從結果我們可以我們兩次獲取到的是同一實例。上述實現方式只是衆多實現方式中的一種,要將其改爲多個實例的情況也十分簡單。

但是,上述Singleton實現在多線程環境下不能正常工作,假定第一個線程完成:
if
 (pInstance == NULL)
檢測後,CPU將控制權交給第二個(甚至是第三個、第四個...)線程,則最終我們可能獲得n個而不是1個實例。以下實現方式同樣無法保證線程安全:

static
 Singleton* Instance() {
    static
 Singleton _instance;
    return
 &_instance;
}


下面的代碼模擬了該問題(由於涉及多線程問題,爲了簡化問題,以下代碼用Java編寫):

class
 Singleton {
    private static
 Singleton singleton = null;
    
    protected
 Singleton() {
        try
 {
            Thread.currentThread().sleep(50);
        }
 catch(InterruptedException ex) {
        }
    }

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

        return
 singleton;
    }
}


public class
 SingletonTest {
    private static
 Singleton singleton = null;
    
    public static
 void main(String[] args) throws InterruptedException {
        // Both threads call Singleton.getInstance().
        Thread threadOne = new Thread(new SingletonTestRunnable()),
            threadTwo = new Thread(new SingletonTestRunnable());
        
        threadOne.start();
        threadTwo.start();
        
        threadOne.join();
        threadTwo.join();
    }

    
    private static class
 SingletonTestRunnable implements Runnable {
        public
 void run() {
            // Get a reference to the singleton.
            Singleton s = Singleton.getInstance();
            
            // Protect singleton member variable from multithreaded access.
            synchronized(SingletonTest.class) {
            if
(singleton == null) // If local reference is null set it to the singleton
               singleton = s;
            }

            if
 (s == singleton)
                System.out.println("Yes.");
            else

                System.out.println("No.");
        }
    }
}


要保證結果的正確性,必須對獲取Singleton實例的方法進行同步控制,爲此,有人提出了double-checked locking的解決方案,相應的getInstance實現如下所示:

public static
 Singleton getInstance() {
    if
(singleton == null) {
        synchronized(Singleton.class) {
            if
(singleton == null) {
                singleton = new Singleton();
            }
        }
    }

    return
 singleton;
}


但據說,由於Java中,在變量singleton完成初始化前,即
singleton = new Singleton();
執行完畢前,sigleton的值可能是任意的,因此,若此時某個線程進入第一個if判斷,則可能判斷結果爲false,從而返回一個無效的引用。因此,在Java中,唯一安全的方法是synchronized整個getInstance。

六、擴展
當系統中存在多個不同類型的Singleton時,可以考慮將Abstract Factory與Singleton模式結合,實現SingletonFactory,以下是一個SingletonFactory的例子:

import java.util.HashMap;

public class
 SingletonFactory {
    public static
 SingletonFactory singletonFactory = new SingletonFactory();
    private static
 HashMap map = new HashMap();
    
    protected
 SingletonFactory() {
        // Exists only to defeat instantiation.
    }
    public static
 synchronized Singleton getInstance(String classname) {
        if
 (classname == null) throw new IllegalArgumentException("Illegal classname");
        
        Singleton singleton = (Singleton)map.get(classname);
        
        if
(singleton != null) {
            return
 singleton;
        }

        if
 (classname.equals("SingeltonSubclass1"))
            singleton = new SingletonSubclass1();    // SingletonSubclass1 derives from Singleton class or implements Singleton interface
        else if(classname.equals("SingeltonSubclass2"))
            singleton = new SingletonSubclass2();    // SingletonSubclass2 derives from Singleton or implements Singleton interface
        
        map.put(classname, singleton);
        return
 singleton;
    }
}


最後,需要注意的是,在C++中,單件對象的釋放是個比較麻煩的事情,在上面的第一個示例中,由於沒有釋放單件對象,實際上是存在內存泄漏的,在釋放單件對象的問題上,我們有以下幾種選擇:
1
、在程序退出時由上層應用負責delete,比如在MFC程序中,在ExitInstance中逐一檢查SingletonFactory的靜態map對象所保存的Singleton;
2
、在Singleton內部採用靜態auto_ptr來封裝Singleton對象,按此方法修改後的示例1如下所示:
#include <assert.h>
#include <iostream>
#include <memory>
using namespace std;

class
 Singleton
{

public
:
    static
 Singleton* Instance() {
        if
 (instance.get() == NULL)
        {

            instance.reset(new Singleton());
        }

        return
 instance.get();
    }

protected
:
    Singleton() {}
private
:
    static
 auto_ptr<Singleton> instance;
    Singleton(const Singleton&) ;
    Singleton& operator=(const Singleton&) ;
};


auto_ptr<Singleton> Singleton::instance = auto_ptr<Singleton>(NULL);

int
 main()
{

    Singleton* pInstance1 = Singleton::Instance(); // Good
    Singleton* pInstance2 = Singleton::Instance();

    cout.setf(ios::hex, ios::basefield);
    cout << "pInstance1 = " << pInstance1 << endl;
    cout << "pInstance2 = " << pInstance2 << endl;
    cout.setf(ios::dec, ios::basefield);
    assert(pInstance1 == pInstance2);
    
    return
 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章