顾名思义,单例模式即应用了此模式的类最多只能实例化出一个对象,此外该类还应该自己管理这个对象,并提供可供外部访问的接口。
应用了单例模式的的类的结构一般应该包括:要实例化的对象,构造函数以及 getInstance() 函数。其中,要实例化的对象应为私有的静态实例,构造函数也应为私有的构造方法, getInstance() 函数为共有的静态方法,用来判断该类是否已经被实例化,没有则调用构造函数实例化出一个对象。
用C++实现单例模式的代码如下:
#include <iostream>
using namespace std;
class Singleton{
private:
static Singleton instance;
Singleton()
{
cout << "Singleton Instance" << endl;
}
public:
static Singleton GetInstance();
};
Singleton Singleton:: GetInstance()
{
cout << "begin" << endl;
if(&instance != NULL){
//这里涉及到一个问题,类的静态成员函数只可以访问类的静态成员变量和静态成员函数,那为什么这里可以调用构造函数呢?
//原因是类的静态成员没有 this 指针,而类的非静态成员函数会隐式的传入一个 this 指针,用来调用对应对象方法或变量。而构造函数本身没有传入 this 指针,实际上它隐式返回了一个 this指针。
return Singleton();
}
cout << "end" << endl;
}
int main()
{
Singleton s = Singleton::GetInstance();
return 0;
}
单例模式最多只能实例化出一个对象,因此必然会设计到多线程的问题。因为多个线程时,调用 getInstance() 函数可能会创建多个实例。 我们需要在代码中添加锁:
1 class LockClass
2 {
3 public:
4 LockClass() {InitializeCriticalSection(m_cs); }
5 lock() {EnterCriticalSection(m_cs); }
6 unlock() {LeaveCriticalSection(m_cs); }
7 ~LockClass() {DeleteCriticalSection(m_cs); }
8
9 private:
10 LPCRITICAL_SECTION m_cs;
11 };
12
13
14 LockClass globalLock;
15
16 class Singleton
17 {
18 private:
19 Singleton();
20
21 public:
22 static Singleton& GetInstance()
23 {
24 globalLock.lock();
25 static Singleton instance;
26 globalLock.unlock();
27 return instance;
28 }
29 int someMethod();
30 };
31
32
33 void func()
34 {
35 ....
36 //call SomeMethod() when needed
37 Singleton::GetInstance()->SomeMethod();
38 ....
39
但是添加锁之后可以看到每次执行 getInstance() 函数时都会执行加锁于解锁的操作,对性能造成很大影响,所以可以采用双重锁定的方法(我觉得更应该叫双重判断):
1 class LockClass
2 {
3 public:
4 LockClass() {InitializeCriticalSection(m_cs); }
5 lock() {EnterCriticalSection(m_cs); }
6 unlock() {LeaveCriticalSection(m_cs); }
7 ~LockClass() {DeleteCriticalSection(m_cs); }
8
9 private:
10 LPCRITICAL_SECTION m_cs;
11 };
12
13 class Singleton
14 {
15 private:
16 Singleton(){}
17 public:
18 static Singleton* m_instance;
19 static LockClass m_lock;
20 static Singleton* getInstance();
21
22 int SomeMethod();
23 };
24
25
26 //<! 在你的.cpp文件头部, 对static 变量进行初始化
27 Singleton* Singleton::m_instance = NULL;
28 LockClass Singleton::m_lock;
29
30 Singleton* Singleton::getInstance()
31 {
32 if(NULL == m_instance)
33 {
34 m_lock.lock();
35 if(NULL == m_instance)
36 {
37 m_instance = new Singleton;
38 }
39 m_lock.UnLock();
40 }
41 return m_instance;
42 }
43
44
45 void func()
46 {
47 ....
48 //call SomeMethod() when needed
49 Singleton::GetInstance()->SomeMethod();
50 ....
51 }
除了这种方法外,还有一种静态初始化的方法,也可以达到同样的效果,而且不需要重复进行加锁解锁的操作。这里的静态初始化指在程序加载时就进行初始化(发生在 main() 函数之前,如果静态变量是一个基本数据类型但是初始值不是常量或静态变量是一个类对象),其他的静态初始化还包括编译初始化(如果静态变量是一个基本数据类型而且初始值时常量)和使用时初始化(发生在变量第一次被引用时,哪个进程先访问这个变量,就有哪个线程负责初始化该变量,static variables with bloks scrope 静态局部变量 一定时使用时初始化)。
可以看出,加载时初始化与编译初始化都没有进行到程序运行空间,因此不涉及到线程安全问题。
实现代码:
class Singleton{
private:
static const Singleton instance;
Singleton()
{
cout << "Singleton Instance" << endl;
}
public:
static const Singleton GetInstance();
};
const Singleton Singleton:: GetInstance()
{
return Singleton();
}
const Singleton Singleton::instance = Singleton::GetInstance();
在Java中,有一个初始化块的概念,在实现单例模式时非常方便。块是"{ }"中包含的代码,逻辑上可以看作一个整体。
class A {
static {
System.out.println("Static init A.");//静态初始化块
}
{
System.out.println("Instance init A.");//初始化块
}
A() {
System.out.println("Constructor A.");//构造函数
}
}
初始化块与构造函数的功能基本相同,但执行顺序优先于构造函数,静态初始化块的顺序优先于初始化块。此外,静态初始化块只在类被第一次加载时执行,普通初始化块和构造函数在实例化对象时执行。涉及到继承时,它们的只想顺序为:
父类的静态初始化块;
子类的静态初始化块;
父类的初始化块;
父类的构造函数;
子类的初始化块;
子类的构造函数;
非静态初始化代码块的作用一般代码复用,如果若干个构造函数都有相同的代码段,可以将其总结到代码块中。
最后再提一句,使用自定义类型静态局部变量时一定要注意 SIOF问题