设计模式之单例模式

2018/05/17 设计模式

单例模式,顾名思义,就是当前类只有一个实例,不论被项目中那个类或者对象调用.

想一想

  • 单例类如何创建?
  • 单例类如何销毁?需要销毁么?
  • 如何做到线程安全?
  • 如何测试?
  • 使用场景

分类

单例模式目前比较流行的有两种: 饿汉懒汉

  • 饿汉 饿了肯定会饥不择食,想办法吃点,所以在单例类定义的时候就进行了初始化

  • 懒汉 因为比较懒,所以不到完不得以不会初始化。在第一次使用到单例类时才初始化。

选择和特点

  • 如果要进行线程同步操作,访问量比较大,采用饿汉方式,这样可以实现更好的性能 (以空间换时间);

  • 访问量较小时,采用懒汉实现 (以时间换空间)

实现方式

构造函数私有,添加静态访问接口;

实现方式一 (普通方式)

SingletonDemo.h

class SingletonDemo
{
private:
    SingletonDemo();
    ~SingletonDemo();

public:
    static SingletonDemo * getInstance();
    static void destroyInstance();

    int getAdd( int nValueA, int nValueB);

private:
    static SingletonDemo *m_pInstance;
};

SingletonDemo.cpp

#include "SingletonDemo.h"

SingletonDemo* SingletonDemo::m_pInstance = nullptr;

SingletonDemo::SingletonDemo()
{
}

SingletonDemo::~SingletonDemo()
{
}

SingletonDemo *SingletonDemo::getInstance()
{
    if ( m_pInstance == nullptr)
    {
        m_pInstance = new SingletonDemo;
    }

    return m_pInstance;
}

void SingletonDemo::destroyInstance()
{
    if ( m_pInstance != nullptr)
    {
        delete m_pInstance;
        m_pInstance = nullptr;
    }
}

int SingletonDemo::getAdd(int nValueA, int nValueB)
{
    return (nValueA + nValueB);
}

main.cpp

#include <QCoreApplication>

#include <QDebug>

#include "SingletonDemo.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    int nSum = SingletonDemo::getInstance ()->getAdd (1,2);
    qDebug()<<"nSum:"<<nSum;

    int nRet = a.exec();

    SingletonDemo::getInstance ()->destroyInstance ();

    return nRet;
}

上述实现方式很简单,也很容易看懂。但是不是线程安全的!

如果此时有多个线程同时首次调用方法SingletonDemo::getInstance (),那么,将有可能同时检测到指针m_pInstancenullptr,这样,这两个线程将会同时构造2个实例!

实现方式二 (加锁实现方式)

上述实现方式不是线程安全的,可以通过加锁优化.

核心实现方式

SingletonDemo *SingletonDemo::getInstance()
{
    if ( m_pInstance == nullptr)
    {
        lock();
        if ( m_pInstance == nullptr)
        {
            m_pInstance = new SingletonDemo;
        }
        UnLock();
    }

    return m_pInstance;
}

...
destroy单例类
...

可以看出,在获取单例时,如果为空则加锁,在判断是否为空,如果还为空再进行初始化操作。这样实现方式为什么是线程安全的?

可以这样想想,当第一个线程获取单例时,发现m_pInstance为空,那么直接加锁,再去判断是否为空,当此时另一个线程也来访问时,发现此时m_pInstance为空,想着要初始化单例时,发现资源占用(加锁了),所以只能等待,这样才是线程安全的。

劣势:这样实现方式在平时一般项目中可以,但是在进行大数据操作的时候就有性能瓶颈!

实现方式三 (自动销毁实现方式)

#ifndef SINGLETONDEMO_H
#define SINGLETONDEMO_H


class SingletonDemo
{
private:
    SingletonDemo();
    ~SingletonDemo();

public:
    static SingletonDemo * getInstance();

    int getAdd( int nValueA, int nValueB);

private:
    static SingletonDemo *m_pInstance;

    class GC
    {
    public:
        ~GC()
        {
            if ( m_pInstance != nullptr)
            {
                delete m_pInstance;
                m_pInstance = nullptr;
            }
        }
    };
    static GC m_gc;
};

#endif // SINGLETONDEMO_H
#include "SingletonDemo.h"

SingletonDemo* SingletonDemo::m_pInstance = new SingletonDemo;
SingletonDemo ::GC SingletonDemo::m_gc;

SingletonDemo::SingletonDemo()
{
}

SingletonDemo::~SingletonDemo()
{
}

SingletonDemo *SingletonDemo::getInstance()
{
    return m_pInstance;
}

int SingletonDemo::getAdd(int nValueA, int nValueB)
{
    return (nValueA + nValueB);
}

main.cpp

#include <QCoreApplication>

#include <QDebug>

#include "SingletonDemo.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    int nSum = SingletonDemo::getInstance ()->getAdd (1,2);
    qDebug()<<"nSum:"<<nSum;

    int nRet = a.exec();
    return nRet;
}

程序在结束时,系统会自动调用 SingletonDemo 静态成员函数 GC 的析构函数,对单例进行释放。

实现方式四 (优化方式)

#ifndef SINGLETONDEMO_H
#define SINGLETONDEMO_H

class SingletonDemo
{
private:
    SingletonDemo();
    ~SingletonDemo();

public:
    static SingletonDemo * getInstance();

    int getAdd( int nValueA, int nValueB);
};

#endif // SINGLETONDEMO_H
#include "SingletonDemo.h"

SingletonDemo::SingletonDemo()
{
}

SingletonDemo::~SingletonDemo()
{
}

SingletonDemo *SingletonDemo::getInstance()
{
    static SingletonDemo instance;
    return &instance;
}

int SingletonDemo::getAdd(int nValueA, int nValueB)
{
    return (nValueA + nValueB);
}

main.cpp

#include <QCoreApplication>

#include <QDebug>

#include "SingletonDemo.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    int nSum = SingletonDemo::getInstance ()->getAdd (1,2);
    qDebug()<<"nSum:"<<nSum;

    int nRet = a.exec();
    return nRet;
}

上述实现方式很好的避免了很多的问题,比如单例类的销毁问题,由于单例并没有进行 new 出,所以没有看到用 delete 进行释放。

静态变量存储在静态存储区,在系统退出时会自动释放。

上述实现方式也是比较推荐的方式,当然在实际项目中还要根据需求进行取舍.

参考文章


作者:鹅卵石
时间:2018年5月17日21:07:22
版本:V 0.0.1
邮箱:kevinlq@yeah.net
版权:本博客若无特别声明,均属于作者原创文章,欢迎大家转载分享。但是,
希望您注明来源,并留下原文地址,这是对作者最大的尊重,也是最知识的尊重。

如果您对本文有任何问题,可以在下方留言,或者Email我.

捐赠

如果觉得分享的内容不错,可以请作者喝杯咖啡.


Show Disqus Comments

Search

    欢迎关注我的微信号

    一个不羁的码农

    不羁的程序员

    转载请注明出处!

    Table of Contents