Singleton 单件模式, 及其变体 Double-Checked Locking 双重检查锁模式, 都可以用于确保某个类只有一个对象实例化.两个模式的区别在于: Singleton 模式用于单线程程序中, 而 Double-Checked Locking 模式用于多线程应用程序.
Singleton 模式的意图是: 保证一个类仅有一个实例, 并提供一个访问它的全局访问点.
工作原理: 用一个特殊方法来实例化所需的对象, 其中最关键的就是这个特殊的方法: 1.调用这个方法时, 检查对象是否已经实例化. 如果已经实例化, 该方法仅返回对象的一个引用. 如果尚未实例化, 该方法实例化该对象并返回对此新实例的一个引用. 2.为了确保这是实例化此类型对象的惟一方法, 需要将这个类的构造函数定义为保护或者私有的.
Singleton 模式的本质在于, 应用程序中的每个对象都使用 Singleton 类的同一实例, 而不用必须负责将该实例四处传递给所有要使用它的对象. 在一个协作对象对此实例所做的修改需要为另一个协作对象所见时, 这一点尤为重要.
Singleton 模式的通用结构如下图:
Singleton 的关键特征:
意图: 希望对象只有一个实例, 但没有控制对象实例化的全局对象. 还希望确保所有实体使用该对象相同的实例,而无需将引用传给它们.
问题: 几个不同的客户对象需要引用同一对象, 而且希望确保这种类型的对象数目不超过一个.
解决方案: 保证一个实例.
参与者与协作者: Client 对象只能通过 getInstance 方法创建 Singleton 实例.
效果: Client 对象无需操心是否已经存在 Singleton 实例.这是由 Singleton 自己控制的.
实现: 1. 添加一个类的私有静态成员变量, 引用所需的对象, 初值为 NULL. 2.添加一个公有静态方法, 它在成员变量的值为 NULL 时实例化这个类(并设置成员变量的值). 然后返回该成员变量的值. 3.将构造函数设置为保护或私有, 从而防止任何人直接实例化这个类, 绕过静态构造函数机制.
//实现示例:
1: //Singleton.h
2: #pragma once
3:
4: class Singleton
5: {
6: private:
7: //私有静态成员变量, 引用所需的对象
8: static Singleton s;
9: int i; //数据
10:
11: Singleton(int x);
12:
13: //将赋值操作符及拷贝构造函数都设置成私有的
14: //且故意不实现,因为它们更本不会被调用
15: //声明为私有的, 以防止任何此类复制的动作产生
16: Singleton& operator=(Singleton&);
17: Singleton(const Singleton&);
18:
19: public:
20: //静态方法, 创建 Singleton 实例的惟一方法
21: static Singleton& getInstance();
22: //存取数据操作
23: int getSingletonData();
24: void setSingletonData(int x);
25: };
1: //Singleton.cpp
2: #include "Singleton.h"
3: #include
4:
5: //静态成员数据初始化
6: Singleton Singleton::s(10);
7:
8: Singleton::Singleton(int x): i(x)
9: {
10: std::cout << "Singleton()..." << std::endl;
11: }
12:
13: Singleton& Singleton::getInstance()
14: {
15: std::cout << "The only way to instance Singleton!" << std::endl;
16: return s;
17: }
18: int Singleton::getSingletonData()
19: {
20: std::cout << "getSingletonData() i = " << i << std::endl;
21: return i;
22: }
23: void Singleton::setSingletonData(int x)
24: {
25: std::cout << "setSingletonData() i = " << x << std::endl;
26: i = x;
27: }
1: //test.cpp
2:
3: #include "Singleton.h"
4: #include
5:
6: int main()
7: {
8: Singleton& s = Singleton::getInstance();
9: std::cout << "Singleton::i = " << s.getSingletonData() << std::endl;
10:
11: Singleton& s2 = Singleton::getInstance();
12: s2.setSingletonData(20);
13: std::cout << "Now Singleton::i = " << s.getSingletonData() << std::endl;
14:
15: return EXIT_SUCCESS;
16: }
注: 真正有用的单件对象很少出现, 一般单件应该用于代替全局变量.
Double-Checked Locking Pattern(DBLP): 双重锁模式. DBLP 模式仅适用于多线程应用程序. 在多线程模式中可能会出现一个问题: 假设对 getInstance() 方法的两个调用几乎同时发生, 这种情况可能非常糟糕.
因为 C++ 本身不支持多线程, 没有多线程同步的关键词, C++ 中多线程是用库实现的.
//典型的代码格式如下所示:
1: //如下典型的格式来自Douglas C. Schmidt
2: //和 Tim Harrison 的论文 Double-Checked Locking
3: class Singleton
4: {
5: public:
6: static Singleton *instance (void)
7: {
8: // First check
9: if (instance_ == 0)
10: {
11: // Ensure serialization (guard
12: // constructor acquires lock_).
13: Guard guard (lock_);
14: // Double check.
15: if (instance_ == 0)
16: instance_ = new Singleton;
17: }
18: return instance_;
19: // guard destructor releases lock_.
20: }
21: private:
22: static Mutex lock_;
23: static Singleton *instance_;
24: };
//C++ 中, 可以使用 volatile 关键字, 其中对于 VC++, 2005 及之后的版本才可以.
1: //Scott Meyers 指出, 单独将 pInstance 声明为 volatile
2: //是不安全的, 更安全的形式如下:
3: class Singleton {
4: public:
5: static volatile Singleton* volatile instance();
6: //else ...
7:
8: private:
9: // one more volatile added
10: static volatile Singleton* volatile pInstance;
11: };
12:
13: // from the implementation file
14: volatile Singleton* volatile Singleton::pInstance = 0;
15:
16: volatile Singleton* volatile Singleton::instance()
17: {
18: if (pInstance == 0)
19: {
20: //Lock lock; 根据具体的库实现
21: Lock lock;
22: if (pInstance == 0)
23: {
24: // one more volatile added
25:
26: volatile Singleton* volatile temp =
27: new volatile Singleton;
28: pInstance = temp;
29: }
30: }
31: return pInstance;
32: }
1: //使用 boost 库, 典型的可能如下--(不了解 boost 库)
2: static Singleton* getInstDC()
3: {
4: if(inst_ == 0)
5: {
6: boost::mutex::scoped_lock l(guard_);
7: if(inst_ == 0){
8: inst_ = new Singleton();
9: }
10:
11: return inst_;
12: }
13: }
1: //使用 boost 库,另有人给出的可能实现方法如下:
2: template
3: class CMTSingleton
4: {
5: private:
6: class Creator
7: {
8: public:
9: Creator()
10: {
11: CMTSingleton<_Tp>::instance();
12: }
13: inline void DoNothing() const { }
14: }
15:
16: static Creator creator;
17: };
18:
19: template
20: typename CMTSingleton<_Tp>::Creator CMTSingleton<_Tp>::creator;
一直也没有使用过多线程, 不知上面的方法具体情况会不会有问题.
看到一个使用比较完整的实现, 贴出来, 里面我进行了一些语法上的修改, 因为原代码编译通不过, VS2008
1: //Double-Checked Locking 实现:
2: //同步类:
3: //synobj.h
4:
5: #ifndef SYNOBJ_H
6: #define SYNOBJ_H
7:
8: #include
9:
10: //用宏将赋值和拷贝构造函数设为私有且不实现
11: #define CLASS_UNCOPYABLE(classname)/
12: private:/
13: classname##(const classname##&);/
14: classname##& operator=(const classname##&);
15:
16: class Mutex{
17: CLASS_UNCOPYABLE(Mutex)
18:
19: public :
20: Mutex() :_cs()
21: {
22: InitializeCriticalSection(&_cs);
23: }
24: ~Mutex()
25: {
26: DeleteCriticalSection(&_cs);
27: }
28: void lock()
29: {
30: EnterCriticalSection(&_cs);
31: }
32: void unlock()
33: {
34: LeaveCriticalSection(&_cs);
35: }
36:
37: private :
38: CRITICAL_SECTION _cs; //临界区
39: };
40:
41: class Lock
42: {
43: CLASS_UNCOPYABLE(Lock)
44:
45: public :
46: //explicit,可以阻止隐式转换的发生
47: explicit Lock(Mutex& cs) :_cs(cs)
48: {
49: _cs.lock();
50: }
51:
52: ~Lock()
53: {
54: _cs.unlock();
55: }
56:
57: private :
58: Mutex& _cs; //互斥变量
59: };
60:
61: #endif /*SYNOBJ_H*/
1: //有了同步对象很容易就能够写出如下代码:
2: //singleton.h
3:
4: #ifndef SINGLETON_H
5: #define SINGLETON_H
6:
7: #include "synobj.h"
8:
9: class Singleton
10: {
11: public:
12: static volatile Singleton* Instance()
13: {
14: // Unique point of access
15: if( 0 == _instance)
16: {
17: // 因为 lock 只对 0 == _instance 为 true 才有效
18: //所以为了优化就再加入了一个判断。
19: Lock lock(_mutex);
20: if(0 == _instance)
21: {
22: _instance = new Singleton();
23: atexit(Destroy);
24: // Register Destroy function
25: }
26:
27: }
28:
29: return _instance;
30: }
31:
32: void DoSomething(){ }
33:
34: private :
35: static void Destroy()
36: {
37: // Destroy the only instance
38: if(_instance != 0)
39: {
40: delete _instance;
41: _instance = 0;
42: }
43: }
44: Singleton(){ } // Prevent clients from creating a new Singleton
45: ~Singleton(){} // Prevent clients from deleting a Singleton
46: Singleton(const Singleton&); // Prevent clients from copying a Singleton
47: Singleton& operator=(const Singleton&);
48:
49: private :
50: static Mutex _mutex;
51: static Singleton volatile *_instance; // The one and only instance
52:
53: // 为了防止编译器优化加入 volatile 这个修饰关键字
54: };
55:
56: #endif /*SINGLETON_H*/
1: //singleton.cpp
2:
3: #include "singleton.h"
4:
5: Mutex Singleton::_mutex;
6:
7: // 为了防止编译器优化加入 volatile 这个修饰关键字
8: volatile Singleton* Singleton::_instance = 0;
Java 代码, 注意, 这个是有问题的. 正确的在后面:
1: public class USTax extends Tax
2: {
3: private static USTax instance;
4: private USTax(){ }
5: public static Tax getInstance(){
6: if(instance == null){
7: //检查到 null 之后同步, 然后再检查一次, 确保实例尚未创建
8: synchronied(this){
9: if(instance == null)
10: instance = new USTax();
11: }
12: }
13: return instance;
14: }
15:
16: private syschronized static void doSync(){
17: if(instance == null)
18: instance = new USTax();
19: }
20: };
对 Java 不太了解, 只把代码贴出来,以防以后参考. 上面的方法是有问题的, 因为 Java 编译器本身的优化工作会在构造方法实例化对象之前从构造方法返回指向该对象的引用. 因此, 在 USTax 对象真正完全构造之前. doSync 就可能完成了. 这会带来问题. 而且编译器会 "注意到" "实例" 成员 "没有办法"在两个 if 语句之间改变状态, 所以会优化掉第二个. 可以如下解决这一问题: 利用类装载程序:
1: public class USTax extends Tax
2: {
3: private static class Instance{
4: static final Tax instance = new USTax();
5: }
6: private static USTax instance;
7: private USTax(){ }
8: public static Tax getInstance(){
9: return Instance.instance;
10: }
11: };
因为内部类(Instance)将只被装载一次,所以只会创建一个 USTax 对象.