概述:类自己提供一个全局的实例。
应用:配置文件的读写,设备的管理(如打印机的管理),线程池,数据库的读写。
实现的5种方式:
懒汉式:在单线程模式下没有问题,多线程模式下会出现多个实例。
饿汉式:没有线程的问题,但是即使没有使用到实例也会实例化类,造成资源的浪费。
双重锁校验:
枚举式:
优点:易于操作,只有一个实例,没有多个实例操作的时候出现的混乱。对于需要频繁调用的对象,优化了节约了内存空间和资源。
缺点:不在是用new来实例化类,而是用一个静态的getInstance造成程序员的困惑。职责过重违背了单一职责原则。
- c++
1,饿汉式
class Singleton
{
public:
static Singleton *getInstance();
private:
Singleton();
private:
static Singleton *m_instance;
};
Singleton *Singleton::m_instance = new Singleton();
Singleton *Singleton::getInstance()
{
return m_instance;
}
Singleton::Singleton()
{
}
这种写法的好处是在开始的时候就直接实例化对象,在多线程中是线程安全的。但是带来了2个问题:1,m_instance的指针什么时候被销毁。2,即使没有使用到该对象也实例化了,没有起到延迟实例化的作用,浪费了资源。
对于销毁问题,我们知道程序在结束的时候会析构所有的全局对象,也就是说会析构掉所有的静态变量和静态成员变量。那么我们可以用一个私有的内嵌类来解决这个问题,私有的嵌套类的静态对象的主要作用就是用来析构掉静态对象,并且不提供给外部使用。
class Singleton
{
public:
static Singleton *getInstance();
private:
Singleton();
class Garbo
{
~Garbo()
{
if(Singleton::m_instance) {
delete Singleton::m_instance;
}
}
};
private:
static Singleton *m_instance;
static Garbo m_garbo;
};
添加一个私有的内嵌类总是觉得有不爽,是不是可以有更简单的方法。
我们可以才用静态局部对象来解决这个问题。
Singleton *Singleton::getInstance()
{
static Singleton m_instance;
return &m_instance;
}
在这里我们不用考虑析构的问题。
对于延迟加载的问题就是我们下面的懒汉式。
2,懒汉式
懒汉式,顾名思义就是懒惰时的实例化。
Singleton *Singleton::m_instance = NULL;
Singleton *Singleton::getInstance()
{
if(m_instance == NULL) {
m_instance = new Singleton();
}
return m_instance;
}
现在代码就是在需要的时候才会实例化对象,但是又有了新的问题, 在多线程中,它是线程不安全的(在线程A,B中,同时调用getInstance() , 这时候都判断m_instance为NULL,然后分别实例化对象,这样实例化了2个对象)。解决方式是采用一个双重锁校验,在第一次为NULL的时候锁住,然后再判断一次,然后才实例化。
这里我采用QT的锁。
在类的定义中加一个static QMutex m_mutex;
QMutex Singleton::m_mutex;
Singleton *Singleton::m_instance = NULL;
Singleton *Singleton::getInstance()
{
if(m_instance == NULL) {
QMutexLocker lock(&m_mutex);
if(m_instance == NULL) {
m_instance = new Singleton();
}
}
return m_instance;
}
- Java
1,饿汉式
class Singleton {
private static Singleton singleton = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return singleton;
}
}
2,懒汉式
我们不考虑线程不安全的写法。首先我们可以锁住整个getInstance()。
class Singleton {
private static Singleton singleton;
private Singleton() {}
public static synchronized Singleton getInstance() {
if(singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
这里面有个问题就是每次调用getInstance都会锁住,造成效率的低下。我们可以采用和c++中相同的双重锁校验。
3,双重锁校验
class Singleton {
private static Singleton singleton;
private Singleton() {
}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
到这里我们解决了多线程的问题。但是在这里面有个新的问题就是,java的无序写入(也有叫重排优化)的问题。无序写入的问题是:对象的创建过程是:1,给对象实例分配内存空间 2,调用对象的构造函数进行初始化。 3, 将实例对象指向分配的内存空间。正常情况下应该是123顺序执行的。在无序优化的情况下可能出现:132的顺序。那么问题就来了。在线程1锁住的情况下,执行了132的过程,在执行3的后,线程2来调用对象的时候,这时候对象实例是不为null的,返回后就会出现调用一个没有经过构造函数初始化的对象实例。解决的方法的是可以在静态成员变量前面添加一个volatile。volatile可以保证处理器不会乱序执行。代码就变成了:
class Singleton {
private static volatile Singleton singleton;
private Singleton() {
}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
4,静态内部类
class Singleton {
private static class SineletonHelper {
private static Singleton singleton = new Singleton();
}
private Singleton() {
}
public static Singleton getInstance() {
return SineletonHelper.singleton;
}
}
静态内部类的方式在类的内部只有在引用到对象的时候才会加载内部对象,实现了延迟实例化的目的,同时它也是直接实例化对象保证了线程安全。
5,枚举实现
enum Singleton {
Instance;
}
这种实现方式既是线程安全的,也没有其他4中方式可以通过放射强制调用私有的构造函数来实例化对象,也自己提供了序列化机制,防止反序列化的时候构造对象。
在java的5中实现方式中,枚举实现是最优的,但是实际应用的过程中,却很少有人这些写。主要是这种写法让人感觉到生疏。实际使用中多半情况下都是使用134的实现方式。
参考文档:
设计模式之单例模式二(解决无序写入的问题)(http://www.tuicool.com/articles/vua2mme)