单例模式探索

以前写 OC 中的单例很固定,一直都这样写。后来我就把它放在快捷代码块里面,只要输入 singleton 就直接输出这段代码。

1
2
3
4
5
6
7
8
9
+ (instancetype)instance
{
static Class *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}

一直这样写,并没有关注内部实现。
这两天一直在复习 C++,然后看到了 C++ 的单例模式,发现内部实现还是挺有讲究的。

先来看 OC 中的。
这个方法刚学 OC 的时候就记下来了。

1
void dispatch_once( dispatch_once_t *predicate, dispatch_block_t block);

官方文档的解释是,这个方法让 block 里面的代码在整个应用的生命周期里只执行一次。如果多线程同时调用,这个函数会让线程在 block 执行完之前都同步地等待。只执行一次,顺带帮我们解决多线程问题,这是苹果帮我们封装好的,官方也建议用来写单例的方法!!

然后来看 C++ 的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Class Singleton {
private:
//构造函数
Singleton();

//静态对象
static Singleton* instance;

public:
static Singleton* getInstance {
if(instance == NULL)
instance = new Singleton();
return instance;
}
}

首先,我们要把构造方法设置 private,让别人无法调用构造方法。
C++ 的静态对象和方法就像 OC 中的类对象和类方法,类似在 OC 中是以 + 开头的方法。
把 instance 和 getInstance 方法都设为静态,让类直接调用。

这是一种实现方法,但是并没有考虑到线程安全。
getInstance() 方法应该上锁。

1
2
3
4
5
6
7
static Singleton* getInstance {
lock(); //这里不实现上锁方法,只是探讨单例模式
if(instance == NULL)
instance = new Singleton();
unlock(); //伪
return instance;
}

这样是可行的,但是每次通过 getInstance 方法都会加上一个同步锁。加锁是一个非常耗时的操作,可以避免的时候应该尽量避免。

进一步完善,当 instance 还没有创建的时候才需要加锁,等第二个线程上锁的时候再判断一次是否已经创建了 instance。

1
2
3
4
5
6
7
8
9
static Singleton* getInstance {
if(instance == NULL){
lock(); //伪
if(instance == NULL)
instance = new Singleton();
unlock(); //伪
}
return instance;
}

另外,要保证只能从单例方法获取单例,不能赋值和拷贝。
所以要声明赋值和拷贝方法,但是不实现。

1
2
Singleton(const Singleton&){};
Singleton&operator=(const Singleton&){};

在 C++11 也可以这样:

1
2
Singleton(const Singleton&) = delete;
Singleton&operator=(const Singleton&) = delete;

所以这是一种比较好的实现方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Class Singleton {
private:
//构造函数
Singleton();

//静态对象
static Singleton* instance;

//赋值和拷贝方法
Singleton(const Singleton&){};
Singleton&operator=(const Singleton&){};

public:
static Singleton* getInstance {
if(instance == NULL){
lock(); //伪
if(instance == NULL)
instance = new Singleton();
unlock(); //伪
}
return instance;
}
}

关于类的静态成员。
在 C++ 中,静态成员不属于类的任何一个对象,所以它们并不是在创建类的对象时被定义的,它们不是由类的构造函数初始化的。一般来说不能在类的内部初始化静态成员,必须在类的外部定义和初始化每个静态成员,类似于全局变量。静态成员定义在任何函数之外,一旦被定义,将一直存在于成员的整个生命周期中。
我们可以利用这个特性来写单例模式。

1
2
3
4
5
6
7
8
9
10
11
12
class Singleton {
private:
Singleton();
static Singleton* instance;
public:
static Singleton* getInstance{

return instance;
}
}
//然后在类外初始化
Singleton* Singleton::instance = new Singleton();

这种方法更加简洁,并且也是线程安全的,因为除了函数里面的静态变量,其他的静态变量都是在 main 函数之前就创建好了,单线程创建的,参考 Stack Overflow 上的一个回答。

在 C# 中可以直接在类内初始化。

1
2
3
4
5
6
7
8
9
10
public sealed class Singleton {
private Singleton(){}
private static Singleton instance = new Singleton();
public static Singleton getInstance {
get
{
return instance;
}
}
}

这种方法比上一种二重锁的方法性能要更好。
在 C# 中还有更好的方法。

1
2
3
4
5
6
7
8
9
10
11
12
public sealed class Singleton {
Singleton(){}
public static Singleton getInstance{
get {
return Nested.instance;
}
}
Class Nested{
static Nested(){}
internal static readonly Singleton instance = new Singleton();
}
}

嵌套类,当用到 Nested 类的时候才会调用构造方法创建 instance。
由于无法实现类内初始化 instance,所以这种方法我还不知道 C++ 怎么实现。
以上方法都是参考自《剑指 Offer》。