【剑指offer】面试题2:实现Singleton模式

2017-06-22  本文已影响75人  不会code的程序猿

参考:http://blog.csdn.net/huhaijing/article/details/51756225

题目:

设计一个类,我们只能实现该类的一个实例。
单例模式:保证一个类只有一个实例,并提供一个访问它的全局访问点。
首先,需要保证一个类只有一个实例,在类中,要构造一个实例,就必须调用类的构造函数,如此,为了防止在外部调用类的构造函数而构造实例,需要将构造函数的访问权限标记为protected或private;最后,需要提供给全局访问点,就需要在类中定义一个static函数,返回在类内部唯一构造的实例。

解法1:只适用于单线程环境

// 剑指offer 面试题2 实现Singleton模式
#include <iostream>
using namespace std;

class Singleton
{
public:
    static Singleton* getInstance()
    {
        // 在后面的Singleton实例初始化时,若后面是new Singleton(),则此处不必new;(废话)
        // 若后面是赋值成NULL,则此处需要判断,需要时new
        // 注意!然而这两种方式并不等价!后面的Singleton实例初始化时,new Singleton(),其实是线程安全的,因为static初始化是在主函数main()之前,那么后面的方法岂不是很麻烦。。。。这也是我测试的时候想到的
        /*
        if(m_pInstance == NULL)
        {
        m_pInstance = new Singleton();
        }
        */
        return m_pInstance;
    }

    static void destroyInstance()
    {
        if (m_pInstance != NULL)
        {
            delete m_pInstance;
            m_pInstance = NULL;
        }
    }

private:
    Singleton(){}
    static Singleton* m_pInstance;
};

// Singleton实例初始化
Singleton* Singleton::m_pInstance = new Singleton(); // 前面不能加static,会和类外全局static混淆

// 单线程获取多次实例
void Test1(){
    // 预期结果:两个实例指针指向的地址相同
    Singleton* singletonObj = Singleton::getInstance();
    cout << singletonObj << endl;

    Singleton* singletonObj2 = Singleton::getInstance();
    cout << singletonObj2 << endl;

    Singleton::destroyInstance();
}

int main(){
    Test1();
    getchar();
    return 0;
}

解法2:多线程可运行但效率低下

如果在同一时刻两个线程同时运行到if (m_Instance != NULL),此刻又没有创建实例,那么两个线程都会创建一个实例。为了保证在多线程环境下只生成一个实例,需要加上一个同步锁。

#include <iostream>
#include <mutex>
#include <thread>
#include <vector>
using namespace std;

class Singleton
{
private:
    static mutex m_mutex; // 互斥量

    Singleton(){}
    static Singleton* m_pInstance;

public:
    static Singleton* getInstance(){
        if (m_pInstance == NULL){
            m_mutex.lock(); // 使用C++11中的多线程库
            if (m_pInstance == NULL){ // 两次判断是否为NULL的双重检查
                m_pInstance = new Singleton();
            }
            m_mutex.unlock();
        }
        return m_pInstance;
    }

    static void destroyInstance(){
        if (m_pInstance != NULL){
            delete m_pInstance;
            m_pInstance = NULL;
        }
    }
};

Singleton* Singleton::m_pInstance = NULL; // 所以说直接new 多好啊,可以省去Lock/Unlock的时间
mutex Singleton::m_mutex;


void print_singleton_instance(){
    Singleton *singletonObj = Singleton::getInstance();
    cout << singletonObj << endl;
}

// 多个进程获得单例
void Test1(){
    // 预期结果,打印出相同的地址,之间可能缺失换行符,也属正常现象
    vector<thread> threads;
    for (int i = 0; i < 10; ++i){
        threads.push_back(thread(print_singleton_instance));
    }

    for (auto& thr : threads){
        thr.join();
    }
}

int main(){
    Test1();
    Singleton::destroyInstance();
    getchar();
    return 0;
}

此处进行了两次m_pInstance == NULL的判断,是借鉴了Java的单例模式实现时,使用的所谓的“双检锁”机制。因为进行一次加锁和解锁是需要付出对应的代价的,而进行两次判断,就可以避免多次加锁与解锁操作,同时也保证了线程安全。但是,如果进行大数据的操作,加锁操作将成为一个性能的瓶颈;为此,一种新的单例模式的实现也就出现了。如果不进行两次判断,则每次调用static Singleton* getInstance()都需要先加锁,然后再判断,这样效率更加低下。

解法3:const static 型实例

#include <iostream>
#include <thread>
#include <vector>
using namespace std;

class Singleton
{
private:
    Singleton(){}
    static const Singleton* m_pInstance; //这里和之前的不同
public:
    static Singleton* getInstance(){

        return const_cast<Singleton *>(m_pInstance); // 去掉“const”特性
        // 注意!若该函数的返回值改为const static型,则此处不必进行const_cast静态转换
        // 所以该函数可以改为:
        /*
        const static Singleton* getInstance(){
            return m_pInstance;
        }
        */
    }

    static void destroyInstance(){
        if(m_pInstance != NULL){
            delete m_pInstance;
            m_pInstance = NULL;
        }
    }
};
const Singleton* Singleton::m_pInstance = new Singleton(); // 利用const只能定义一次,不能再次修改的特性,static继续保持类内只有一个实例

void print_singleton_instance(){
    Singleton *singletonObj = Singleton::getInstance();
    cout << singletonObj << endl;
}

// 多个进程获得单例
void Test1(){
    // 预期结果,打印出相同的地址,之间可能缺失换行符,也属正常现象
    vector<thread> threads;
    for(int i = 0; i < 10; ++i){
        threads.push_back(thread(print_singleton_instance));
    }

    for(auto& thr : threads){
        thr.join();
    }
}

int main(){
    Test1();
    Singleton::destroyInstance();
    return 0;
}

解法4:实现按需创建实例

#include <iostream>
#include <thread>
#include <vector>
using namespace std;

class Singleton
{
private:
    Singleton(){}

public:
    static Singleton* getInstance(){
        static Singleton m_pInstance; // 注意,声明在该函数内
        return &m_pInstance;
    }
};

void print_singleton_instance(){
    Singleton *singletonObj = Singleton::getInstance();
    cout << singletonObj << endl;
}

// 多个进程获得单例
void Test1(){
    // 预期结果,打印出相同的地址,之间可能缺失换行符,也属正常现象
    vector<thread> threads;
    for(int i = 0; i < 10; ++i){
        threads.push_back(thread(print_singleton_instance));
    }

    for(auto& thr : threads){
        thr.join();
    }
}

// 单个进程获得多次实例
void Test2(){
    // 预期结果,打印出相同的地址,之间换行符分隔
    print_singleton_instance();
    print_singleton_instance();
}

int main(){
    cout << "Test1 begins: " << endl;
    Test1();
    cout << "Test2 begins: " << endl;
    Test2();
    return 0;
}

其实自己还有好多地方没太明白,先写在这里吧。

上一篇 下一篇

猜你喜欢

热点阅读