劍指offer筆記面試題2----實現Singleton模式

来源:https://www.cnblogs.com/tangliang39/archive/2019/10/17/11694673.html
-Advertisement-
Play Games

題目:設計一個類,我們只能生成該類的一個實例。 解法一:單線程解法 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機制》。*/

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 方案一:在data中給input的值賦一個初始值 方案二:在給input賦值時,使用this.$set ...
  • Tinypng: 地址:https://tinypng.com/ 這款工具最大限度的做到對畫質無損的進行壓縮。 這個工具同時支持對Jpg和Png的壓縮。 Tinypng也支持Wordpress和magento的使用。 > 本文來自[木莊網路博客]> 強烈推薦一款圖片無損壓縮工具 ...
  • 描述在本地測試代碼沒問題,但是部署到伺服器上時就報錯。錯誤> cross-env WEBPACK_TARGET=node NODE_ENV=production node ./server/app.jstruethe server is start at port 3333/usr/share/ng... ...
  • 一、mock 1、簡介 mock是一個模擬數據生成器,旨在幫助前端獨立於後端進行開發,幫助編寫單元測試。其可模擬 Ajax 並返回模擬數據,使前端不用去調用後端的介面,方便測試。 2、vue直接使用mock step1:安裝mock step2:直接引入mock.js,並編寫mock介面(Mock. ...
  • 當頁面引入百度地圖時,谷歌瀏覽器的控制台會彈出一個警告信息,錯誤提示如下圖所示: 解決方案如下: 把api引用url裡面的 api 改為 getscript 將代碼: 修改為: 完... ...
  • Ajax設置自定義請求頭的兩種方法 ...
  • 場景 SpringCloud-服務註冊與實現-Eureka創建服務註冊中心(附源碼下載): https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/102535957 SpringCloud-服務註冊與實現-Eureka創建服務提供者(附源 ...
  • 在 Java 中有多種方法可以比較日期,日期在電腦內部表示為(long型)時間點——自1970年1月1日以來經過的毫秒數。在Java中,Date是一個對象,包含多個用於比較的方法,任何比較兩個日期的方法本質上都會比較日期的時間。 本文主要介紹以下五種方式: 1. 使用 Date.compareTo ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...