快手C++一面初試:手寫單例模式代碼
作為 C++ 開發者,你一定遇到過這樣的場景:日志系統需要全局唯一的實例記錄操作,配置管理器不能重復加載配置文件,數據庫連接池更容不得多實例搶占資源。這些場景的核心訴求驚人地一致 ——確保某個類在程序生命周期中只有一個實例。這正是單例模式大顯身手的地方。它像程序世界里的 "獨生子女證",嚴格管控類的實例數量,同時提供全局訪問點。看似簡單的設計,卻藏著不少坑:多線程環境下的競態條件、內存泄漏風險、拷貝構造函數的防濫用。
今天我們直擊核心,手把手寫透 5 種 C++ 單例實現。從最基礎的懶漢模式到 C++11 的局部靜態變量方案,從線程安全隱患分析到性能優化技巧,每個代碼片段都附帶避坑指南。無論你是剛接觸設計模式的新手,還是想重構老舊單例的老兵,這篇實戰指南都能讓你徹底掌握單例模式的精髓。
Part1.單例模式簡介
1.1單例模式概述
單例模式,從名字上就能看出它的獨特之處 ——“單例”,即單個實例。在 C++ 中,它是一種設計模式,確保一個類僅有一個實例存在于整個程序的生命周期中 ,并且為這個唯一的實例提供一個全局的訪問點。就像在一個大型的軟件系統中,有一個專門負責管理系統配置的類,這個類只需要創建一個實例就可以為整個系統提供服務,如果創建多個實例,不僅會浪費系統資源,還可能導致配置信息的不一致。通過單例模式,我們可以保證這個配置類在程序中只有一個實例,所有需要獲取或修改系統配置的地方都可以通過這個單例實例來進行操作。
1.2生活中的類比
為了更好地理解單例模式,我們可以將它類比到生活中的場景。想象一下,你所在的公司有一位總經理,這位總經理在公司中是獨一無二的存在,他掌握著公司的重要決策權,負責公司的整體運營和發展方向。公司的各個部門,無論是銷售部門、研發部門還是財務部門,在遇到重要決策或需要協調資源時,都會去找總經理。
總經理就像是單例模式中的唯一實例,而各個部門就像是程序中的不同模塊,它們都通過這個唯一的 “總經理” 來獲取決策和資源協調,這就是單例模式在生活中的生動體現。再比如學校里的校長,全校只有一位校長,他管理著學校的各項事務,師生們如果有重要的事情需要學校層面的決策,都會去找校長。校長這個角色就如同單例模式中的類的唯一實例,為整個學校這個 “系統” 提供了統一的管理和決策的入口。
1.3為什么使用單例模式
單例模式在編程中有著廣泛的應用和重要的作用。首先,它可以節省內存資源。因為一個類只有一個實例,不會像普通類那樣被多次實例化,從而減少了內存的占用。在一個大型的游戲開發項目中,游戲的配置信息,如畫面分辨率、音效音量等,這些配置在整個游戲運行過程中只需要一份實例就可以了,如果每個模塊都去創建自己的配置實例,不僅會浪費大量的內存資源,還可能導致不同模塊獲取到的配置不一致,從而引發各種奇怪的問題。這時候單例模式就派上用場啦,通過將配置信息封裝在一個單例類中,整個游戲程序都可以通過這個單例類來獲取和修改配置,既保證了配置的一致性,又節省了內存資源。
其次,單例模式方便管理全局資源。在一個企業級應用中,數據庫連接是一種非常寶貴的資源,創建和銷毀數據庫連接都需要消耗一定的時間和系統資源。如果每個業務模塊都獨立地創建和管理自己的數據庫連接,那么在高并發的情況下,很容易導致系統資源耗盡,性能急劇下降。而使用單例模式來管理數據庫連接,就可以確保整個應用程序中只有一個數據庫連接實例,所有的業務模塊都共享這個連接,大大提高了資源的利用率和系統的性能。
此外,單例模式還可以避免資源的重復占用。例如,在一個多線程的環境中,如果多個線程都嘗試創建同一個資源的實例,可能會導致資源的沖突和不一致。而通過單例模式,我們可以保證這個資源只有一個實例,所有線程都共享這個實例,從而避免了資源的重復占用和沖突。
Part2.手寫C++ 單例模式代碼實現
2.1基本要點剖析
在實現 C++ 單例模式時,有幾個關鍵的要點需要把握。首先,要將構造函數私有化。這是為了防止外部代碼通過new操作符隨意創建類的實例,從而確保整個程序中只有一個實例存在。就好比一個珍貴的限量版藝術品,只有特定的授權人員才能接觸和操作,其他人無法私自復制或創建。例如:
class Singleton {
private:
Singleton() {} // 私有化構造函數
};其次,需要一個私有靜態實例指針來保存唯一的實例。這個指針就像是一個特殊的 “管家”,專門負責管理和指向這個唯一的實例。因為是靜態的,所以它在整個程序運行期間都存在,并且只有一份。示例代碼如下:
class Singleton {
private:
static Singleton* instance; // 私有靜態實例指針
Singleton() {}
};
Singleton* Singleton::instance = nullptr; // 在類外初始化指針為nullptr最后,提供一個公共靜態方法作為訪問實例的接口。通過這個接口,其他代碼就可以獲取到這個唯一的實例,就像通過一扇專門的門進入一個房間,而這個房間里存放著唯一的 “寶藏”(單例實例)。例如:
class Singleton {
public:
static Singleton* getInstance() {
if (instance == nullptr) {
instance = new Singleton();
}
return instance;
}
private:
static Singleton* instance;
Singleton() {}
};
Singleton* Singleton::instance = nullptr;2.2懶漢模式(線程不安全)
class Singleton {
public:
static Singleton* getInstance() {
if (instance == nullptr) {
instance = new Singleton();
}
return instance;
}
private:
static Singleton* instance;
Singleton() {}
};
Singleton* Singleton::instance = nullptr;懶漢模式的核心原理是延遲初始化。它就像一個懶人,只有在真正被需要(第一次調用getInstance方法)的時候,才會去創建實例。在程序啟動時,并不會立即創建實例,而是等到有代碼調用getInstance方法,并且發現instance指針為空時,才會創建一個新的實例并返回。這種方式在一些情況下可以節省資源,因為如果單例實例在整個程序運行過程中都沒有被使用,那么就不會浪費資源去創建它。
然而,懶漢模式在多線程環境下存在嚴重的問題。想象一下,有多個線程同時調用getInstance方法,并且都判斷instance為空。由于線程的并發執行,這些線程可能會同時進入創建實例的代碼塊,從而導致創建多個實例,這就違背了單例模式的初衷。例如,在線程 A 判斷instance為空后,還沒來得及創建實例時,線程 B 也判斷instance為空,然后兩個線程都創建了各自的實例,這樣就破壞了單例的唯一性。
2.3懶漢模式(線程安全 - 加鎖實現)
#include <mutex>
class Singleton {
public:
static Singleton* getInstance() {
std::lock_guard<std::mutex> lock(mutex);
if (instance == nullptr) {
instance = new Singleton();
}
return instance;
}
private:
static Singleton* instance;
static std::mutex mutex;
Singleton() {}
};
Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mutex;為了解決懶漢模式的線程安全問題,可以使用互斥鎖。這里使用了std::lock_guard,它是一個 RAII(Resource Acquisition Is Initialization)類,在構造時自動加鎖,在析構時自動解鎖。當一個線程進入getInstance方法時,std::lock_guard會自動加鎖,這樣其他線程就無法進入這個代碼塊,直到當前線程解鎖。在加鎖后,再判斷instance是否為空,如果為空則創建實例,從而保證了在多線程環境下只有一個實例被創建。
雖然加鎖實現保證了線程安全,但也帶來了性能開銷。每次調用getInstance方法時都需要加鎖和解鎖,而加鎖和解鎖操作是有一定時間成本的。如果這個方法被頻繁調用,那么頻繁的加鎖解鎖操作會降低程序的執行效率。就像每次進入一個房間都要進行繁瑣的安檢(加鎖)和解除安檢(解鎖),會大大影響進入房間的速度。在高并發場景下,這種性能開銷可能會成為系統的瓶頸。
2.4懶漢模式(線程安全 - 雙重檢查鎖定 DCL)
#include <mutex>
class Singleton {
public:
static Singleton* getInstance() {
if (instance == nullptr) {
std::lock_guard<std::mutex> lock(mutex);
if (instance == nullptr) {
instance = new Singleton();
}
}
return instance;
}
private:
static Singleton* instance;
static std::mutex mutex;
Singleton() {}
};
Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mutex;雙重檢查鎖定(DCL)是對加鎖實現的進一步優化。它的原理是進行兩次檢查。第一次檢查instance是否為空,如果不為空,直接返回實例,這樣就避免了不必要的加鎖操作,提高了性能。只有當第一次檢查發現instance為空時,才會進入加鎖代碼塊。在加鎖后,再進行第二次檢查,這是因為可能有多個線程同時通過了第一次檢查,所以需要在加鎖后再次確認instance是否為空,確保只有一個實例被創建。這種方式既保證了線程安全,又在一定程度上減少了加鎖帶來的性能開銷。
在 C++11 之前,雙重檢查鎖定存在內存屏障問題。由于編譯器和 CPU 的優化,可能會導致指令重排序,使得一個線程在instance還未完全初始化時就返回了它,從而導致其他線程訪問到未初始化的對象。為了解決這個問題,在 C++11 之前需要使用volatile關鍵字來修飾instance指針,防止指令重排序。在 C++11 及之后,內存模型得到了改進,std::mutex已經保證了內存的可見性和順序性,所以在一般情況下不需要再顯式使用volatile關鍵字,但在一些特殊場景下,仍然需要根據具體情況進行考慮。
2.5餓漢模式
class Singleton {
public:
static Singleton* getInstance() {
return instance;
}
private:
static Singleton* instance;
Singleton() {}
};
Singleton* Singleton::instance = new Singleton();餓漢模式與懶漢模式相反,它在類加載時就創建實例。當程序啟動,類被加載到內存中時,instance就已經被創建好了。因為類加載過程是由系統控制的,在這個過程中不存在多線程競爭,所以天然是線程安全的。之后,任何代碼調用getInstance方法,都會直接返回已經創建好的實例。
餓漢模式的優點很明顯,實現簡單,不需要考慮復雜的線程安全問題,因為實例在類加載時就已經創建,不存在多線程并發創建的情況。而且由于不需要加鎖解鎖,性能也相對較高。然而,它也有缺點,那就是可能會造成資源浪費。如果這個單例實例在整個程序運行過程中都沒有被使用,那么在程序啟動時就創建它就會白白占用內存資源。就像提前準備好了一件可能永遠都不會用到的工具,占據了寶貴的空間。
2.6局部靜態變量實現(C++11 及以上)
class Singleton {
public:
static Singleton& getInstance() {
static Singleton instance;
return instance;
}
private:
Singleton() {}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};在 C++11 及以上版本中,可以利用局部靜態變量的特性來實現單例模式。當getInstance方法第一次被調用時,會創建局部靜態變量instance。C++11 保證了局部靜態變量的初始化是線程安全的,即多個線程同時調用getInstance方法,只會有一個線程能夠初始化instance,其他線程會等待初始化完成。這種方式實現了延遲初始化,只有在第一次調用getInstance方法時才創建實例,同時又保證了線程安全。
這種實現方式具有很多優勢。首先,它非常簡潔,代碼量少,不需要復雜的加鎖操作或雙重檢查。其次,它自動處理了內存回收問題,當程序結束時,局部靜態變量會自動銷毀,不需要手動管理內存。此外,它還天然地防止了拷貝和移動操作,通過刪除拷貝構造函數和賦值運算符重載,確保了單例的唯一性不會被破壞。這種方式在 C++11 及以上版本中是實現單例模式的一種推薦方式,兼具簡潔性、安全性和高效性。
Part3.不同實現方式對比
3.1性能對比
從時間復雜度來看,餓漢模式在程序啟動時就創建實例,后續獲取實例的操作時間復雜度為 O (1),因為不需要任何判斷和創建操作,直接返回已創建好的實例。而懶漢模式(非線程安全和線程安全 - 加鎖實現)在第一次獲取實例時需要進行判斷和創建操作,時間復雜度為 O (1),但線程安全 - 加鎖實現每次獲取實例都需要加鎖解鎖,這部分操作雖然時間復雜度也是 O (1),但加鎖解鎖本身有一定的時間開銷。
雙重檢查鎖定(DCL)優化后的懶漢模式,在正常情況下(實例已創建),第一次檢查就可以直接返回實例,時間復雜度為 O (1),只有在第一次創建實例時才會有加鎖操作,相對減少了鎖競爭,提高了性能。局部靜態變量實現同樣在第一次調用getInstance方法時創建實例,時間復雜度為 O (1),并且由于 C++11 保證了局部靜態變量初始化的線程安全性,后續調用也無需額外的同步操作,性能表現較好。
在空間復雜度方面,所有實現方式在正常情況下都只創建一個實例,空間復雜度為 O (1)。但如果考慮到線程安全的實現方式中使用的互斥鎖等同步機制,會額外占用一些內存空間,不過這個額外空間通常較小,一般可以忽略不計 。在多線程并發環境下獲取實例時,餓漢模式由于實例已經提前創建好,不存在多線程競爭創建的問題,性能較為穩定。而懶漢模式的非線程安全版本在多線程環境下會出現創建多個實例的錯誤情況,無法保證單例的正確性,也就談不上性能了。
懶漢模式的線程安全 - 加鎖實現,每次獲取實例都加鎖,在高并發場景下,頻繁的加鎖解鎖操作會導致線程上下文切換頻繁,嚴重影響性能。雙重檢查鎖定雖然減少了鎖競爭,但在多線程并發時,仍可能存在一定的鎖沖突,不過相比每次都加鎖的方式,性能有了顯著提升。局部靜態變量實現利用 C++11 的特性,在多線程并發獲取實例時,能夠高效地保證線程安全,且沒有額外的鎖開銷,性能表現最佳。
3.2資源占用對比
懶漢模式和餓漢模式在內存占用和資源初始化時間上存在明顯差異。餓漢模式在程序啟動時就創建實例,這意味著無論這個實例在后續是否被使用,它所占用的內存資源都會一直存在,直到程序結束。如果單例實例占用的內存較大,或者初始化時需要加載大量的數據或執行復雜的初始化操作,那么在程序啟動階段就會消耗較多的內存和系統資源,可能會導致程序啟動時間變長。例如,在一個圖形渲染引擎中,如果將渲染器的配置信息管理類設計為餓漢式單例,并且這個配置類在初始化時需要讀取大量的圖形資源配置文件,那么在程序啟動時就會占用較多的內存和磁盤 I/O 資源,影響程序的啟動速度。
懶漢模式則是延遲初始化,只有在第一次調用getInstance方法時才會創建實例。這在一定程度上節省了內存資源,因為如果單例實例在整個程序運行過程中都沒有被使用,那么就不會占用內存空間。然而,這種延遲初始化也帶來了一些問題。在多線程環境下,為了保證線程安全,需要使用同步機制,如加鎖或雙重檢查鎖定,這些同步機制會增加代碼的復雜性和執行開銷。而且,第一次創建實例時可能會因為初始化操作而導致一定的性能抖動,如果這個初始化操作比較耗時,可能會影響到程序的實時性。比如在一個實時通信系統中,如果將通信連接管理類設計為懶漢式單例,并且在第一次創建實例時需要進行復雜的網絡連接初始化和認證操作,那么在第一次使用該單例時,可能會導致通信延遲,影響用戶體驗。
3.3使用場景建議
根據應用場景的線程環境和資源需求等因素,可以合理選擇不同的單例模式實現方式。如果應用場景是單線程環境,那么非線程安全的懶漢模式就可以滿足需求,因為它實現簡單,且能實現延遲初始化,節省資源。例如,在一個簡單的命令行工具中,由于不存在多線程并發的情況,使用非線程安全的懶漢模式來實現單例類,既可以保證單例的唯一性,又能減少不必要的同步開銷。
在多線程環境下,如果單例實例占用的資源較少,或者初始化開銷不大,且對線程安全要求較高,餓漢模式是一個不錯的選擇。因為它實現簡單,天生線程安全,在程序啟動時就創建實例,避免了多線程并發創建帶來的問題。比如在一個小型的多線程服務器程序中,用于管理服務器基本配置信息的單例類,由于配置信息占用資源較少,使用餓漢模式可以快速創建實例,并且保證在多線程環境下的安全訪問。
如果單例實例占用資源較大,且希望實現延遲初始化,同時保證多線程安全,雙重檢查鎖定(DCL)的懶漢模式或局部靜態變量實現是比較好的選擇。在 C++11 及以上版本中,局部靜態變量實現更為推薦,因為它代碼簡潔,利用了 C++11 的線程安全特性,性能高效。
例如,在一個大型的游戲開發項目中,用于管理游戲場景資源的單例類,由于場景資源占用內存較大,使用局部靜態變量實現的單例模式,可以在第一次使用場景資源時才進行加載和初始化,同時保證多線程環境下的安全訪問,提高游戲的性能和資源利用率。如果項目需要兼容舊版本的 C++,那么雙重檢查鎖定的懶漢模式可以在保證線程安全的前提下,盡量減少鎖帶來的性能開銷 。
Part4.案例分析實戰
4.1定義
一個類僅能存在唯一實例,且該實例需由類自身創建,并向整個系統提供一個全局的訪問方式。具體要點如下:
- 該類不允許存在多個實例,僅有一個實例是其核心特征;
- 實例的創建過程由類自身完成,無需外部干預;
- 類要主動為整個系統提供獲取該實例的途徑。
4.2單例模式結構代碼
singleton.h文件代碼如下:
#include "Singleton.h"
#include <iostream>
// 初始化靜態成員變量
Singleton* Singleton::instance = nullptr;
// 構造函數實現
Singleton::Singleton()
{
std::cout << "Singleton instance created" << std::endl;
}
// 獲取實例的實現
Singleton* Singleton::getInstance()
{
if (instance == nullptr)
{
instance = new Singleton();
}
return instance;
}
// 示例方法實現
void Singleton::showMessage()
{
std::cout << "Hello from Singleton!" << std::endl;
}singleton.cpp文件代碼如下:
#ifndef SINGLETON_H
#define SINGLETON_H
class Singleton
{
public:
// 提供全局訪問點
static Singleton* getInstance();
// 示例方法,用于測試
void showMessage();
protected:
// 保護構造函數,防止外部實例化
Singleton();
private:
// 唯一實例的靜態指針
static Singleton* instance;
};
#endif // SINGLETON_Hmain.cpp文件代碼如下:
#include "Singleton.h"
int main()
{
// 獲取單例實例并使用
Singleton* singleton = Singleton::getInstance();
singleton->showMessage();
// 驗證單例特性(再次獲取實例應該是同一個對象)
Singleton* anotherSingleton = Singleton::getInstance();
if (singleton == anotherSingleton)
{
std::cout << "Both instances are the same - singleton works!" << std::endl;
}
return 0;
}編譯時,需要將這三個文件一起編譯(例如:g++ Singleton.cpp main.cpp -o singleton),然后運行生成的可執行文件即可。
4.3打印機實例
singleton.h文件代碼如下:
#ifndef SINGLETON_H
#define SINGLETON_H
#include <mutex>
#include <string>
// 打印機單例類
class Printer
{
public:
// 禁止復制和賦值,確保實例唯一性
Printer(const Printer&) = delete;
Printer& operator=(const Printer&) = delete;
// 獲取全局唯一打印機實例
static Printer* getInstance();
// 打印文檔方法
void printDocument(const std::string& documentName);
// 獲取打印隊列長度
int getPrintQueueLength() const;
// 銷毀打印機實例
static void destroyInstance();
protected:
// 保護的構造函數,防止外部直接創建實例
Printer();
// 保護的析構函數,確保正確銷毀
~Printer();
private:
// 靜態指針指向唯一實例
static Printer* instance;
// 線程安全鎖
static std::mutex mtx;
// 打印隊列長度計數器
int printQueueLength;
};
#endif // SINGLETON_Hsingleton.cpp文件代碼如下:
#include "singleton.h"
#include <iostream>
#include <chrono>
#include <thread>
// 初始化靜態成員變量
Printer* Printer::instance = nullptr;
std::mutex Printer::mtx;
// 構造函數:初始化打印隊列
Printer::Printer() : printQueueLength(0)
{
std::cout << "打印機實例已初始化" << std::endl;
}
// 析構函數:清理資源
Printer::~Printer()
{
std::cout << "打印機實例已銷毀" << std::endl;
}
// 獲取全局唯一實例(線程安全)
Printer* Printer::getInstance()
{
// 雙重檢查鎖定,提高多線程環境下的性能
if (instance == nullptr)
{
std::lock_guard<std::mutex> lock(mtx); // 加鎖確保線程安全
if (instance == nullptr)
{
instance = new Printer();
}
}
return instance;
}
// 打印文檔實現
void Printer::printDocument(const std::string& documentName)
{
std::lock_guard<std::mutex> lock(mtx); // 確保打印操作的線程安全
printQueueLength++;
std::cout << "\n開始打印文檔: " << documentName << std::endl;
std::cout << "當前打印隊列長度: " << printQueueLength << std::endl;
// 模擬打印過程
std::this_thread::sleep_for(std::chrono::seconds(2));
std::cout << "文檔 " << documentName << " 打印完成" << std::endl;
printQueueLength--;
std::cout << "當前打印隊列長度: " << printQueueLength << std::endl;
}
// 獲取打印隊列長度
int Printer::getPrintQueueLength() const
{
return printQueueLength;
}
// 銷毀實例
void Printer::destroyInstance()
{
std::lock_guard<std::mutex> lock(mtx);
if (instance != nullptr)
{
delete instance;
instance = nullptr;
}
}main.cpp文件代碼如下:
#include "singleton.h"
#include <iostream>
#include <thread>
#include <vector>
#include <string>
// 打印任務函數,用于多線程測試
void printTask(const std::string& documentName)
{
// 獲取打印機實例
Printer* printer = Printer::getInstance();
// 執行打印
printer->printDocument(documentName);
}
int main()
{
std::cout << "=== 打印機單例模式測試 ===" << std::endl;
// 測試1:單線程打印
std::cout << "\n--- 單線程打印測試 ---" << std::endl;
Printer* printer = Printer::getInstance();
printer->printDocument("報告.pdf");
printer->printDocument("圖片.png");
// 測試2:多線程打印(模擬多個應用程序同時使用打印機)
std::cout << "\n--- 多線程打印測試 ---" << std::endl;
std::vector<std::thread> threads;
// 創建5個打印線程
for (int i = 1; i <= 5; ++i)
{
std::string docName = "文檔" + std::to_string(i) + ".txt";
threads.emplace_back(printTask, docName);
}
// 等待所有線程完成
for (auto& t : threads)
{
t.join();
}
// 測試3:驗證單例唯一性
std::cout << "\n--- 單例唯一性驗證 ---" << std::endl;
Printer* printer2 = Printer::getInstance();
if (printer == printer2)
{
std::cout << "驗證成功:兩次獲取的是同一個打印機實例" << std::endl;
}
else
{
std::cout << "驗證失敗:獲取了不同的打印機實例" << std::endl;
}
// 銷毀打印機實例
Printer::destroyInstance();
return 0;
}Makefile文件:
CXX = g++
CXXFLAGS = -std=c++11 -Wall -Wextra -pthread
TARGET = printer_app
SRCS = main.cpp singleton.cpp
OBJS = $(SRCS:.cpp=.o)
HEADER = singleton.h
all: $(TARGET)
$(TARGET): $(OBJS)
$(CXX) $(CXXFLAGS) -o $@ $^
@echo "編譯完成: $(TARGET)"
%.o: %.cpp $(HEADER)
$(CXX) $(CXXFLAGS) -c $< -o $@
clean:
rm -f $(OBJS) $(TARGET)
@echo "清理完成"
run: $(TARGET)
./$(TARGET)
.PHONY: all clean run運行效果如下所示:
=== 打印機單例模式測試 ===
--- 單線程打印測試 ---
打印機實例已初始化
開始打印文檔: 報告.pdf
當前打印隊列長度: 1
文檔 報告.pdf 打印完成
當前打印隊列長度: 0
開始打印文檔: 圖片.png
當前打印隊列長度: 1
文檔 圖片.png 打印完成
當前打印隊列長度: 0
--- 多線程打印測試 ---
開始打印文檔: 文檔1.txt
當前打印隊列長度: 1
文檔 文檔1.txt 打印完成
當前打印隊列長度: 0
開始打印文檔: 文檔2.txt
當前打印隊列長度: 1
文檔 文檔2.txt 打印完成
當前打印隊列長度: 0
開始打印文檔: 文檔3.txt
當前打印隊列長度: 1
文檔 文檔3.txt 打印完成
當前打印隊列長度: 0
開始打印文檔: 文檔4.txt
當前打印隊列長度: 1
文檔 文檔4.txt 打印完成
當前打印隊列長度: 0
開始打印文檔: 文檔5.txt
當前打印隊列長度: 1
文檔 文檔5.txt 打印完成
當前打印隊列長度: 0
--- 單例唯一性驗證 ---
驗證成功:兩次獲取的是同一個打印機實例
打印機實例已銷毀- 程序啟動后首先顯示測試標題,然后進行單線程打印測試
- 首次獲取打印機實例時會初始化并顯示 "打印機實例已初始化"
- 每個打印任務會顯示開始打印、當前隊列長度、打印完成等信息
- 多線程測試中,由于加鎖機制,打印任務會按順序執行(實際多線程環境可能有不同的執行順序,但隊列計數始終準確)
- 單例唯一性驗證通過,確認兩次獲取的是同一個實例
- 程序結束時銷毀打印機實例,顯示 "打印機實例已銷毀"
實際運行時,多線程部分的輸出順序可能因系統調度而有所不同,但始終會保證打印隊列計數的正確性和實例的唯一性。每次打印會有 2 秒的延遲(模擬實際打印耗時)。































