物件池
物件池模式使用一組已初始化的、隨時可用的物件,而不是按需分配和銷燬它們。池的客戶端將從池中請求一個物件,並在返回的物件上執行操作。當客戶端完成操作時,它會將物件(這是一種特定型別的工廠物件)返回池中,而不是銷燬它。
在初始化類例項成本高、類例項化率高且任何時刻使用例項數量低的場景中,物件池可以提供顯著的效能提升。當建立新物件(尤其是透過網路)可能需要可變時間時,池化物件可以在可預測的時間內獲取。
然而,這些優勢主要適用於那些在時間上昂貴的物件,例如資料庫連線、套接字連線、執行緒和大型圖形物件,例如字型或點陣圖。在某些情況下,簡單的物件池(不包含外部資源,只佔用記憶體)可能效率低下,並可能降低效能。
處理空池
物件池採用三種策略之一來處理池中沒有備用物件時的請求。
- 無法提供物件(並向客戶端返回錯誤)。
- 分配一個新物件,從而增加池的大小。執行此操作的池通常允許您設定高水位標記(曾經使用的最大物件數)。
- 在多執行緒環境中,池可能會阻塞客戶端,直到另一個執行緒將物件返回池。
陷阱
在編寫物件池時,程式設計師必須小心確保返回池的物件狀態重置為下一個使用物件的合理狀態。如果沒有觀察到這一點,物件通常會處於客戶端程式未預期的狀態,並可能導致客戶端程式失敗。池負責重置物件,而不是客戶端。充滿處於危險過時狀態的物件的池有時被稱為物件汙水池,並被視為反模式。
過時狀態的存在並不總是問題;當過時狀態的存在導致物件的行為發生改變時,它就會變得危險。例如,表示身份驗證詳細資訊的物件如果在傳遞出去之前沒有重置“成功身份驗證”標誌,可能會出現問題,因為它將表明使用者已成功身份驗證(可能是其他人),而他們還沒有嘗試身份驗證。但是,如果您只是沒有重置一些僅用於除錯的值,例如最後使用的身份驗證伺服器的標識,它就可以正常工作。
物件重置不充分也可能導致資訊洩露。如果物件包含機密資料(例如使用者的信用卡號碼),而這些資料在物件傳遞給新客戶端之前沒有清除,惡意或有錯誤的客戶端可能會將資料洩露給未經授權的方。
如果池被多個執行緒使用,它可能需要防止並行執行緒並行獲取和嘗試重用同一物件的方法。如果池化物件是不可變的或執行緒安全的,則不需要這樣做。
批評
一些出版物不建議在某些語言中使用物件池,例如 Java,特別是對於只使用記憶體而不包含外部資源的物件。反對者通常認為,在具有垃圾回收機制的現代語言中,物件分配相對較快;雖然運算子new只需要十條指令,但在池化設計中找到的經典new-delete對需要數百條指令,因為它執行更復雜的工作。此外,大多數垃圾回收器會掃描“活動”物件引用,而不是這些物件用於其內容的記憶體。這意味著任何數量的沒有引用的“死亡”物件都可以以很小的代價丟棄。相比之下,保持大量“活動”但未使用的物件會延長垃圾回收的持續時間。在某些情況下,使用垃圾回收而不是直接管理記憶體的程式可能會執行得更快。
示例
在 .NET 基本類庫中,有一些物件實現了這種模式。System.Threading.ThreadPool 被配置為具有預定義數量的執行緒來分配。當執行緒返回時,它們可用於其他計算。因此,人們可以使用執行緒,而無需支付建立和處置執行緒的成本。
Java 透過java.util.concurrent.ExecutorService和其他相關類支援執行緒池。執行器服務具有一定數量的“基本”執行緒,這些執行緒永遠不會被丟棄。如果所有執行緒都很繁忙,服務會分配允許數量的額外執行緒,這些執行緒如果在一定時間內未使用,則會稍後被丟棄。如果不再允許使用更多執行緒,則任務可以放入佇列。最後,如果此佇列可能變得過長,它可以配置為掛起請求執行緒。
