欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

剑指offer笔记面试题2----实现Singleton模式

程序员文章站 2022-05-18 19:30:46
题目:设计一个类,我们只能生成该类的一个实例。 解法一:单线程解法 c++ //缺点:多线程情况下,每个线程可能创建出不同的的Singleton实例 include using namespace std; class Singleton { public: static Singleton get ......

题目:设计一个类,我们只能生成该类的一个实例。

解法一:单线程解法

    //缺点:多线程情况下,每个线程可能创建出不同的的singleton实例
    #include <iostream>
    using namespace std;
    
    class singleton
    {
    public:
        static singleton* getinstance()
        {
            if(m_pinstance == nullptr)
            {
                m_pinstance = new singleton();
            }
            return m_pinstance;
        }
    
        static void destroyinstance()
        {
            if(m_pinstance != nullptr)
            {
                delete m_pinstance;
                m_pinstance = nullptr;
            }    
        }
    private:
        singleton(){}
        static singleton* m_pinstance;
    };
    
    singleton* singleton::m_pinstance = nullptr;
    
    // 单线程获取多次实例
    void test1(){
        // 预期结果:两个实例指针指向的地址相同
        singleton* singletonobj = singleton::getinstance();
        cout << singletonobj << endl;
        singleton* singletonobj2 = singleton::getinstance();
        cout << singletonobj2 << endl;
        singleton::destroyinstance();
    }
    
    int main(){
        test1();
        return 0;
    }

解法二:多线程+加锁

    /*解法一是最简单,也是最普遍的实现方式。但是,这种实现方式有很多问题,比如没有考虑多线程的问题,在多线程的情况下,就可能会创建多个singleton实例,以下是改善的版本。*/
    #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 == nullptr){
                m_mutex.lock(); // 使用c++11中的多线程库
                if(m_pinstance == nullptr){ // 两次判断是否为null的双重检查
                    m_pinstance = new singleton();
                }
                m_mutex.unlock();
            }
            return m_pinstance;
        }
    
        static void destroyinstance(){
            if(m_pinstance != nullptr){
                delete m_pinstance;
                m_pinstance = nullptr;
            }
        }
    };
    
    singleton* singleton::m_pinstance = nullptr;
    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();
        return 0;
    }
    /*此方法中进行了两次m_pinstance == nullptr的判断,使用了所谓的“双检锁”机制。因为进行一次加锁和解锁是需要付出对应的代价的,而进行两次判断,就可以避免多次加锁与解锁操作,只在m_pinstance不为nullptr时才需要加锁,同时也保证了线程安全。但是,如果进行大数据的操作,加锁操作将成为一个性能的瓶颈,为此,一种新的单例模式的实现也就出现了。*/

解法三: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;
    }
    /*因为静态初始化在程序开始时,也就是进入主函数之前,由主线程以单线程方式完成了初始化,所以静态初始化实例保证了线程安全性。在性能要求比较高时,就可以使用这种方式,从而避免频繁的加锁和解锁造成的资源浪费。由于上述三种实现,都要考虑到实例的销毁,关于实例的销毁,待会在分析。*

解法四:在get函数中创建并返回static临时实例的引用

    //ps:该方法不能认为控制单例实例的销毁
    #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;
    }

解法五:最终方案,最简&显式控制实例销毁

    /*在实际项目中,特别是客户端开发,其实是不在乎这个实例的销毁的。因为,全局就这么一个变量,全局都要用,它的生命周期伴随着软件的生命周期,软件结束了,他就自然而然结束了,因为一个程序关闭之后,它会释放它占用的内存资源的,所以,也就没有所谓的内存泄漏了。
    但是,有以下情况,是必须要进行实例销毁的:
        在类中,有一些文件锁了,文件句柄,数据库连接等等,这些随着程序的关闭而不会立即关闭的资源,必须要在程序关闭前,进行手动释放。*/
    #include <iostream>
    #include <thread>
    #include <vector>
    using namespace std;
    
    class singleton
    {
    private:
        singleton(){}
        static singleton* m_pinstance;
    
        // **重点在这**
        class gc // 类似java的垃圾回收器
        {
        public:
            ~gc(){
                // 可以在这里释放所有想要释放的资源,比如数据库连接,文件句柄……等等。
                if(m_pinstance != null){
                    cout << "gc: will delete resource !" << endl;
                    delete m_pinstance;
                    m_pinstance = null;
                }
            };
        };
    
        // 内部类的实例
        static gc gc;
    
    public:
        static singleton* getinstance(){
            return m_pinstance;
        }
    };
    
    
    singleton* singleton::m_pinstance = new singleton();
    singleton::gc singleton::gc;
    
    void print_instance(){
        singleton* obj1 = singleton::getinstance();
        cout << obj1 << endl;
    }
    
    // 多线程获取单例
    void test1(){
        // 预期输出:相同的地址,中间可能缺失换行符,属于正常现象
        vector<thread> threads;
        for(int i = 0; i < 10; ++i){
            threads.push_back(thread(print_instance));
        }
    
        for(auto& thr : threads){
            thr.join();
        }
    }
    
    // 单线程获取单例
    void test2(){
        // 预期输出:相同的地址,换行符分隔
        print_instance();
        print_instance();
        print_instance();
        print_instance();
        print_instance();
    }
    
    int main()
    {
        cout << "test1 begins: " << endl;
        cout << "预期输出:相同的地址,中间可以缺失换行(每次运行结果的排列格式通常不一样)。" << endl;
        test1();
        cout << "test2 begins: " << endl;
        cout << "预期输出:相同的地址,每行一个。" << endl;
        test2();
        return 0;
    }
    /*在程序运行结束时,系统会调用singleton的静态成员gc的析构函数,该析构函数会进行资源的释放,而这种资源的释放方式是在程序员“不知道”的情况下进行的,而程序员不用特别的去关心,使用单例模式的代码时,不必关心资源的释放。
    那么这种实现方式的原理是什么呢?由于程序在结束的时候,系统会自动析构所有的全局变量,系统也会析构所有类的静态成员变量,因为静态变量和全局变量在内存中,都是存储在静态存储区的,所有静态存储区的变量都会被释放。由于此处是用了一个内部gc类,而该类的作用就是用来释放资源。这种技巧在c++中是广泛存在的,参见《c++中的raii机制》。*/