创建型模式:Singleton 单例模式
目录

单例模式:确保一个类只创建一个实例,并提供一个全局访问点。

常用:threadpool, cache, 数据库连接,对话框,偏好设置,注册表对象,日志对象,设备驱动程序对象。

单例模式比全局变量的好处:延迟实例化,保证对象只有一个。

C++实现

经典实现:

// 返回单例对象指针
// .h
class Singleton {
public:
static Singletion *getInstance();
private:
Singleton();
Singleton(const Singleton&);         // Don't implement
void operator=(const Singleton&);    // Don't implement

static Singleton *_instance;
};

// .c
Singleton* Singleton::_instance = NULL;
Singleton* Singleton::getInstance() {
if (_instance == NULL) {              // lazy init
_instance = new Singleton;
}
return _instance;
}

由于C++没有GC,上面这个经典用法是在heap上new了一个对象。 这存在一个问题:单例对象需要手动delete,否则造成memory leak,进而资源得不到释放。 还有一点:getInstance是否要返回单例对象的指针?毕竟C++下用引用更对多一些。 针对以上两点,参考链接1给出了一种更好的用法。

// 返回静态单例对象引用

class Singleton {
public:
static Singleton& getInstance() {
static Singleton _instance;      // lazy init, auto destruction and thread safe(C++11)
return _instance;
}
private:
Singleton() {}
Singleton(const Singleton const&);   // Don't Implement.
void operator=(const Singleton&);    // Don't implement
};

看懂这段代码,还要知道C++中静态变量生命周期问题。简单的说,函数的静态变量只有在第一次访问时才进行初始化(与函数不同,class的静态成员变量初始化时间不确定,可能在程序开始初始化)。 所以上面这段代码满足lazy init的需求。 并且在程序退出时会自动执行_instance的析构函数。

线程安全性

在第一个版本中,其在多线程情况下会出现new多次的情况,因此其不是线程安全的版本。

Singleton* Singleton::getInstance() {
if (_instance == NULL) {
_instance = new Singleton;          // not thread safe
}
return _instance;
}

经典的解决方法是对new的语句加同步锁并前后两次判断实例(double-checked locking)是否已存在。

Singleton* Singleton::getInstance() {
if (_instance == NULL) {
Lock lock(mutex);
if (_instance == NULL) {
_instance = new Singleton;
}
}
return _instance;
}

但因为new是一个多步骤的操作,包括申请内存并返回地址和调用初始构造函数,因此可能_instance已经不为NULL了可是对象构造函数并没有被调用。因此这在C++的多线程情况下还是有问题。 可以使用以下方法解决:

volatile Singleton *_instance;

Singleton* Singleton::getInstance() {
if (_instance == NULL) {
Lock lock(mutex);
if (_instance == NULL) {
Singleton *s = new Singleton;               // barrier
_instance = s;                  // assign and constructor are done
}
}
return _instance;
}

第二个版本的代码是线程安全的,因为C++静态对象的初始化是线程安全的(C++11)。 所以目前看来其是单例模式的C++实现版本中比较优的一个。

Java 版本

Java可以有以下几种方式实现线程安全的Singleton模式:

  1. 把getInstance()声明为synchronized。
  2. 使用“急切”创建实例,而不用延迟实例化的方法。
  3. 把instance成员声明为volatile弱同步,使用双重加锁方式。

这里实现第三种方法,这种方法只有第一次会同步。

public class Singleton {
private volatile static Singleton instance;

private Singleton() {}

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

反对意见

这篇文章Singletons: Solving problems you didn’t know you never had since 1995反对使用单例模式。文章中的观点认为,单例模式提供了两点特性:

  1. 保证对象只有一个instance存在;
  2. 保证这个instance全局可访问;

而作者认为,这两个特性是都是糟糕的特性,因为:

  1. 在敏捷大潮下,需求随时都会改变,从设计上使用Singleton将导致需求改变后,代码调整起来很复杂。Singleton使代码的可扩展性(flexibility)降低;
  2. 全局变量更是万恶之源,它影响的代码的复用性、并行性,使得需要针对使用了全局变量的函数单独进行单元测试;

支持作者论点的论据就是c++标准库的std::coutstd::ostream()。 前者就是后者的唯一一个实例,但是却没有使用Singleton模式来设计。 因此无需为了Singleton的第一点特性来引入第二点特性。

参考

  1. C++ Singleton design pattern
  2. GOF: Design Patterns
  3. head first Design Patterns

发表评论