單例模式,是一種創建型設計模式,他的核心思想是保證一個類只有一個實例(即,在整個應用程序中,只存在該類的一個實例對象,而不是創建多個相同類型的對象),并提供一個全局訪問點來訪問這個實例(即,其他類只能通過一個,可以是靜態方法,來獲取到這個唯一實例)。
優勢:因為只有一個實例,既避免了多次創建相同的對象,節省了系統資源。多個類,也就是模塊之間可以通過單例實例來共享數據;同時方便我們站在全局的角度控制唯一實例的訪問(即,可以嚴格的控制客戶怎樣訪問它、何時訪問他);此外,單例模式還允許只在需要時才進行實例化,即所謂的懶加載,以提高程序的性能。
實現一個單例設計模式需要滿足以下的基本要求:首先,任何外部代碼不能夠隨意創建實例,也就意味著類的構造函數只能私有;其次,任何外部代碼也不能夠隨意訪問實例中的任何資源,也就意味著所有的靜態實例變量須是私有的;最后,需要設置一個公有的靜態方法,以便外部能夠獲取到實例的內部資源。
單例模式的實現方式有:懶漢式(只有在遇到請求實例時才會創建一個實例,并且如果已經創建過,就會返回已有的實例)、餓漢式(類加載時就已經完成了實例的創建,不管創建的實例在后面會不會使用,先創建再說)等。
在多線程環境下,由于餓漢式在程序啟動階段就完成實例的初始化,因此不存在多個線程同時嘗試初始化實例的問題;但是懶漢式中多個線程同時訪問 GetInstance() 方法,并且在同一時刻檢測到實例沒有被創建(只要線程切換足夠頻繁就有可能發生),就可能會同時創建實例,從而導致多個實例被創建,(好比你和小明都發現家里每米了,在你們沒有相互通知的情況下,都會去超市買一袋米,這樣就重復購買了,違背了單例模式)這種情況下我們可以采用一些同步機制,例如使用互斥鎖來確保在任何時刻只有一個線程能夠執行實例的創建。
以下是單例設計模式的基本寫法,以Java代碼為例:
- 餓漢式:
public class Singleton {private static final Singleton instance = new Singleton(); // 類加載時就被創建private Singleton() {// 私有構造方法,防止外部實例化}public static Singleton getInstance() {return instance;}
}
- 懶漢式 + 互斥鎖:
public class Singleton {private static final Singleton instance;private Singleton() {// 私有構造方法,防止外部實例化}public static synchronized Singleton getInstance() {if (instance == null) {instance == new Singleton();}return instance;}
}
- 懶漢式 + 雙重檢查鎖提高性能:
public class Singleton {private static final Singleton instance;private Singleton() {// 私有構造方法,防止外部實例化}public static Singleton getInstance() {if (instance == null) {synchronized (Singleton.class) {if (instance == null) {instance == new Singleton();}}}return instance;}
}
在Java中,關鍵字synchronized用于實現線程的同步。
首先,synchronized可以用來修飾方法,表示該方法在同一時間只能被一個線程訪問。當一個線程訪問該方法時,其他線程必須等待該線程執行完畢才能訪問該方法。
其次,synchronized還可以用來修飾代碼塊。當多個線程需要訪問共享資源時,可以使用synchronized來保證同一時間只有一個線程可以訪問該資源。synchronized代碼塊使用一個對象作為鎖,當一個線程進入synchronized代碼塊時,會嘗試獲取該對象的鎖,如果鎖被其他線程占用,則該線程被阻塞,直到鎖被釋放。
synchronized的作用是避免多個線程對共享資源的并發訪問導致的數據不一致或者錯誤。通過使用synchronized,可以保證在同一時間只有一個線程對共享資源進行訪問,從而保證了線程安全。
"類.class"是Java編程中的一種語法結構,用于獲取某個類的Class對象。在Java中,每個類都對應一個Class對象,該對象包含了有關類的結構、字段、方法等信息,可以在程序運行時通過反射機制來訪問和操作類的成員。
通過類名后面添加".class",可以獲得該類的Class對象。例如,"String.class"返回String類的Class對象,"Integer.class"返回Integer類的Class對象。
使用Class對象可以進行一些操作,比如實例化對象、訪問類的靜態成員、獲取類的父類和接口等。
請注意,類.class只能用于獲取該類的Class對象,不能用于實例化這個類的對象。要實例化一個類的對象,可以使用Class對象中的newInstance()方法或者使用構造函數來創建對象。
總結一下,我們應當什么時候使用單例設計模式:
- 如果多個模塊都需要共享某一種資源,例如一個全局的配置管理器來存儲管理配置信息、管理數據庫連接池信息等,可以使用單例設計模式。
- 如果創建對象本身就比較消耗資源,而且可能在整個程序中都不一定會使用,就可以使用單例模式中的懶加載;
- 有些場景中只需要一個實例就足以協調所有行為,創建多個實例沒有必要甚至會導致不好的后果,例如管理應用程序中的緩存、管理線程池。
管理應用程序中的緩存只需要一個緩存實例的原因是為了保持數據的一致性和避免沖突。
當應用程序使用多個緩存實例時,可能會導致以下問題:
數據一致性:如果多個緩存實例各自獨立管理數據,那么在不同實例中的數據可能會不一致。當更新或刪除數據時,如果沒有及時同步所有的緩存實例,可能導致數據的不一致性,從而引發潛在的問題。
資源浪費:每個緩存實例都需要占用內存和其他資源,如果使用多個緩存實例,會造成資源的浪費。而只使用一個緩存實例可以更有效地利用資源。
緩存沖突:多個緩存實例可能會導致相同的數據被同時緩存在不同的實例中,從而造成緩存沖突。如果多個實例同時讀寫相同的數據,可能會引發并發問題,影響應用程序的性能和正確性。
通過只使用一個緩存實例,可以確保數據的一致性,并最大程度地節省資源。可以使用單例模式來創建和管理緩存實例,確保應用程序中只存在一個緩存對象。這樣可以簡化緩存管理的操作,提高系統的可靠性和性能。
【設計模式專題之單例模式】1.小明的購物車
?CPP版本:
#include <iostream>
#include <vector>
#include <map>using namespace std;class ShoppingCartManager {
public:// 獲取購物車實例static ShoppingCartManager& getInstance() {static ShoppingCartManager instance;return instance;}// 添加商品到購物車void addToCart(const string& itemName, int quantity) {if (cart.find(itemName) == cart.end()) {order.push_back(itemName);}cart[itemName] += quantity;}// 查看購物車void viewCart() const {for (const auto& itemName : order) {cout << itemName << " " << cart.at(itemName) << endl; // 不能使用cart[itemName],因為處在一個const方法,不能有修改變量值的格式出現。}}private:// 私有構造函數ShoppingCartManager() {}// 購物車存儲商品和數量的映射map<string, int> cart;// 記錄加入購物車的順序vector<string> order;
};int main() {string itemName;int quantity;while (cin >> itemName >> quantity) {// 獲取購物車實例并添加商品ShoppingCartManager& cart = ShoppingCartManager::getInstance();cart.addToCart(itemName, quantity);}// 輸出購物車內容const ShoppingCartManager& cart = ShoppingCartManager::getInstance();cart.viewCart();return 0;
}
在C++中,static關鍵字可以有多種作用:
靜態變量(Static Variables):在函數內使用static關鍵字,可以創建靜態變量。靜態變量在函數調用結束后仍然存在,并且在下一次調用函數時保持其值不變。這使得靜態變量在需要記住上一次函數調用結果或在多次調用之間共享數據時非常有用。
靜態成員變量(Static Member Variables):在類內部使用static關鍵字,可以創建靜態成員變量。靜態成員變量屬于類本身而不是類的實例,因此在不創建類的對象時也可以訪問和修改它們。靜態成員變量在類的所有實例之間共享數據。
靜態函數(Static Functions):在類內部使用static關鍵字,可以創建靜態函數。靜態函數不依賴于類的實例,因此可以直接通過類名調用,無需創建類的對象。靜態函數主要用于處理和類本身相關的操作,而不是與類的實例數據交互。
靜態類(Static Classes):在C++中,可以將類聲明為static類。靜態類的成員函數和成員變量都必須是靜態的,而且無法創建靜態類的對象。靜態類主要用于實現一些全局可訪問的、不需要創建對象的功能,類似于命名空間的作用。
總的來說,C++中的static關鍵字可以用于創建靜態變量、靜態成員變量、靜態函數以及靜態類,它們都具有特定的作用和用途。