1. 程式人生 > >物件池的優劣

物件池的優劣

物件池的優點: 複用池中物件, 沒有分配記憶體和建立堆中物件的開銷, 沒有釋放記憶體和銷燬堆中物件的開銷, 進而減少垃圾收集器的負擔, 避免記憶體抖動; 不必重複初始化物件狀態, 對於比較耗時的constructor和finalize來說非常合適;


物件池的缺點:
(1)現在Java的物件分配操作不比c語言的malloc呼叫慢, 對於輕中量級的物件, 分配/釋放物件的開銷可以忽略不計;
(2)併發環境中, 多個執行緒可能(同時)需要獲取池中物件, 進而需要在堆資料結構上進行同步或者因為鎖競爭而產生阻塞, 這種開銷要比建立銷燬物件的開銷高數百倍;
(3)由於池中物件的數量有限, 勢必成為一個可伸縮性瓶頸;
(4)很難正確的設定物件池的大小, 如果太小則起不到作用, 如果過大, 則佔用記憶體資源高, 可以起一個執行緒定期掃描分析, 將池壓縮到一個合適的尺寸以節約記憶體,但為了獲得不錯的分析結果, 在掃描期間可能需要暫停複用以避免干擾(造成效率低下), 或者使用非常複雜的演算法策略(增加維護難度);
(5)設計和使用物件池容易出錯, 設計上需要注意狀態同步, 這是個難點, 使用上可能存在忘記歸還(就像c語言程式設計忘記free一樣), 重複歸還(可能需要做個迴圈判斷一下是否池中存在此物件, 這也是個開銷), 歸還後仍舊使用物件(可能造成多個執行緒併發使用一個物件的情況)等問題;


物件池有其特定的適用場景:
(1)受限的, 不需要可伸縮性的環境(比如移動裝置): cpu效能不夠強勁, 記憶體比較緊張, 垃圾收集, 記憶體抖動會造成比較大的影響, 需要提高記憶體管理效率, 響應性比吞吐量更為重要;
(2)數量受限的資源, 比如資料庫連線; (自己寫比較容易埋坑, 建議使用成熟的庫方案, 比如c3p0)
(3)建立成本高昂的物件, 可斟酌是否池化, 比較常見的有執行緒池, 位元組陣列池等; (如果有, 則建議使用成熟的庫方案, 比如jdk自帶的ThreadPoolExecutor, 而不是自己寫)


android在支援庫中提供了池的構件單元–Pools, 其中Pools.SimplePool實現比較簡單, 採用固定長度的陣列和後進先出的策略, 池中物件的記憶體不會再被釋放, 直到池本身被垃圾回收; Pools.SynchronizedPool僅僅是在Pools.SimplePool基礎上加了個同步鎖;
當然也可以使用apache commons pool2類庫作為池的構件單元;

注:
響應性是指一個任務單元需要”多快”能處理完;
吞吐量是指一定的計算資源(如CPU, 記憶體, 硬碟容量, IO頻寬)和單位時間內, 能完成”多少”任務;
可伸縮性是指增加計算資源(如CPU, 記憶體, 硬碟容量, IO頻寬), 程式的吞吐量的能得到多大程度的增加;
Example1:
並行GC縮短了每次stop-the-world的時間來提供響應性, 但做完一次full-GC的任務量要比序列GC耗費更長的時間(吞吐量降低);
Example2:
程式分為彼此獨立的表現層/業務邏輯層/持久化層會提供可伸縮性, 但由於在不同系統間傳遞資料, 比對應的未使用分層模型的程式要慢;

以上參考來自於<java併發程式設計實戰>, <effective java>, <java程式效能優化-讓你的Java程式更快, 更穩定>, 均反對濫用池化技術;