Overview of Content
本文將深入探討「Object Pool
」設計模式,從概念到實現進行詳細解說
首先,將介紹Object Pool的概念及其在軟體開發中的使用場景,接著,將展示 Object Pool
的 UML
示意圖,以便讀者更好地理解其結構和運作方式
在進入具體實現之前,我們會簡單說明 Object Pool
設計模式的優缺點,幫助讀者在適當的情況下做出適切的選擇。隨後,將深入討論如何實現 Object Pool
,包括標準的實現方法以及如何配合 Java虛擬機(JVM
)的垃圾回收機制。通過這些內容,讀者將能夠全面了解Object Pool設計模式,並在實際應用中獲得更好的效果。
Object Pool 概述、使用場景
當你的業務邏輯需要在一個時間區間 創建大量的物件時,就可以提前使用 Object Pool
設計用來優化性能,提高應用效率
Object Pool UML
● 把對象池化的本地是 一次性初始化所有對象,減少物件在初始化(new
)耗費的時間,透這種個操作可以間接提高效能
類 | 功能 |
---|---|
ObjectPool<T> | 泛型類,有兩個主要責任,內部 1.操作緩存池的邏輯,並 2.一次性初始化所需的緩存大小 |
ConcretePool<Data> | 實做 ObjectPool |
Data | 實際要被緩存的子物件 |
Object Pool 設計優缺點
● Object Pool
設計優點
● 在使用時僅第一次需要耗費時間創建 instance
,之後的使用都 不必須要經過物件初始化 的行為就可以使用,提高了性能效率
可以減少 JVM 創建物件耗費的時間、效能(像是併發時
CAS
比較、物件初始化… 等等行為)
● Object Pool
設計缺點
● 如果沒有適時的安排時機釋放物件,仍是佔用內存空間的一種行為,而這可能導致大量 GC
,操作會因為 GC
而變慢
● 增加程式的複雜度,如果是處於簡易場合建議不要使用
Object Pool 實做
Object Pool 標準
A. ObjectPool
類:這個類有兩個重點事項要完成
● 操作緩存池的邏輯(取出 checkout
、放置 checkin
)
● 一次性初始化所需的緩存大小:這裡預設該池中預設有 1 個物件
abstract class ObjectPool<T> {
inner class ObjectStatus {
var isFree = true
}
private val pool = mutableMapOf<T, ObjectStatus>()
init {
val tmp = create()
// 預設物件
pool[tmp] = ObjectStatus()
}
abstract fun lazyCreate(): () -> T
fun create(): T {
return lazyCreate().invoke()
}
@Synchronized
fun checkout(): T? {
for ((key, item) in pool.entries) {
if (!item.isFree) {
continue
}
return key.also {
// 標誌該物件已經不可被使用
item.isFree = false
}
}
return null
}
@Synchronized
fun checkIn(t: T) {
var findKey = false
for ((key, item) in pool.entries) {
// 判斷物件是否相同
if(key == t) {
findKey = true
item.isFree = true
break
}
}
if (!findKey) {
pool[t] = ObjectStatus()
println("add now instance to pool.")
}
}
}
● 除了基本使用之外,在實際使用中 還要考慮到 池(
Pool
)的大小、最大值、池狀態、異常處理、回收... 等等狀況
B. ConcretePool<Data>
、Data
類
● ConcretePool<Data>
類:該類繼承於 ObjectPool
類,是 池對象的實做
class MyDataPool: ObjectPool<DataBean>() {
override fun lazyCreate(): () -> DataBean {
return {
DataBean()
}
}
}
● Data
類:這邊 覆寫 equals
、hashCode
兩個函數,因為在 ObjectPool
中會用來比較
data class DataBean constructor(var who: String = "", var msg: String = "") {
override fun toString(): String {
return "name=($who), msg=($msg), hashcode=(${hashCode()})"
}
override fun equals(other: Any?): Boolean {
return other is DataBean &&
other.msg == msg &&
other.who == who
}
override fun hashCode(): Int {
var result = who.hashCode()
result = 31 * result + msg.hashCode()
return result
}
}
● 使用物件池:以下是測試步驟
A. 取出(checkout
)物件 2 次:第一次可以正常取得預設物件,而第二次由於池中已經沒有物件可取,所以返回 null
B. 放置(checkin
)物件 2 次:將預設物件、新創物件放回池中
這時池中就有 2 個可用物件了~
C. 取出(checkout
)物件 3 次:由於池內只有 2 個物件,如果取出三次那第三次的物件就為 null
fun main() {
val pool = MyDataPool()
// 第一次取物件
val reuseBean = pool.checkout()?.apply {
msg = "Hello World"
who = "Alien"
// 正常可取得
println("First time checkout: $this")
}
// 第二次取物件
pool.checkout().apply {
// 池中無物件,返回 null
println("Second time checkout: $this")
}
println()
// 放置原預設物件
println("First time check in.")
pool.checkIn(reuseBean!!)
// 放置新物件
println("Second time check in.")
pool.checkIn(DataBean())
println()
repeat(3) {
pool.checkout().apply {
println("repeat time=${it.plus(1)}, $this")
}
}
}
設置引用 - 配合 JVM GC 機制
這裡使用 Java 為範例,因為 Kotlin 的靜態檢查較為完整,要模擬出 GC & 物件回收較為麻煩
● 由於上述的範例中,並沒有包括到何時回收物件,所以這裡特別 使用 JVM GC、類引用的特性來再創建一個可自動回收的 Object Pool
這裡使用簡單靜態工廠來作為 Pool
● 簡單 Bean 類
class MyBean {
final String message;
MyBean(String message) {
this.message = message;
}
}
● Object Pool 實作:這裡的重點是 使用 WeakReference
引用來存儲物件,當未來 GC 回收時,該物件會自動被回收
public class RefObjectPool {
// 使用 WeakReference 來作為 Value,其內部才真正包裹數據
private final HashMap<String, WeakReference<MyBean>> refCacheObject = new HashMap<>();
public MyBean valueOf(String message) {
for (Map.Entry<String, WeakReference<MyBean>> item : refCacheObject.entrySet()) {
String key = item.getKey();
MyBean value = item.getValue().get();
if (value != null && key.equals(message)) {
return value;
}
}
MyBean newBean = new MyBean(message);
refCacheObject.put(message, new WeakReference<>(newBean));
return newBean;
}
}
● 測試 ObjectPool
功能:測試它是否有物件暫存的功能
public static void main(String[] args) throws InterruptedException {
RefObjectPool factory = new RefObjectPool();
String apple = "Apple";
String banana = "Banana";
MyBean bean1 = factory.valueOf(apple);
System.out.println("bean1: " + bean1.hashCode());
MyBean bean2 = factory.valueOf(banana);
System.out.println("bean2: " + bean2.hashCode());
MyBean bean3 = factory.valueOf(apple);
System.out.println("bean3: " + bean3.hashCode());
System.out.println("Bean1 == Bean2: " + (bean1.hashCode() == bean2.hashCode()));
System.out.println("Bean1 == Bean3: " + (bean1.hashCode() == bean3.hashCode()));
}
從下圖結果可以看出來 ObjectPool 類確實有暫存物件的功能
● 測試 JVM GC & WeakReference 的關係:來看看 GC 是否會影響到 ObjectPool 物件存取原來的物件,如果影響到 ObjectPool 存的物件,那物件暫存就會失效!(這是好事,這時可以把物件回收的責任交付給 JVM 負責)
public static void main(String[] args) throws InterruptedException {
RefObjectPool factory = new RefObjectPool();
String apple = "Apple";
String banana = "Banana";
MyBean bean1 = factory.valueOf(apple);
System.out.println("bean1: " + bean1.hashCode());
MyBean bean2 = factory.valueOf(banana);
System.out.println("bean2: " + bean2.hashCode());
MyBean bean3 = factory.valueOf(apple);
System.out.println("bean3: " + bean3.hashCode());
System.out.println("Bean1 == Bean2: " + (bean1.hashCode() == bean2.hashCode()));
System.out.println("Bean1 == Bean3: " + (bean1.hashCode() == bean3.hashCode()));
apple = banana = null;
bean1 = bean2 = bean3 = null;
System.gc();
Thread.sleep(1000);
apple = "Apple";
banana = "Banana";
MyBean afterGCBean1 = factory.valueOf(apple);
System.out.println("afterGCBean1: " + afterGCBean1.hashCode());
MyBean afterGCBean2 = factory.valueOf(banana);
System.out.println("afterGCBean2: " + afterGCBean2.hashCode());
}
● 從上圖結果中我們可以看到,假設我們不使用
GC
、Thread#sleep()
那會得到緩存在記憶體中的相同物件(同 hashcode)
更多的物件導向設計
物件導向的設計基礎如下,如果是初學者或是不熟悉的各位,建議可以從這些基礎開始認識,打好基底才能走個更穩(在學習的時候也需要不斷回頭看)!
創建模式 Creation Patterns
● 創建模式 PK
● 創建模式 - Creation Patterns
:
創建模式用於「物件的創建」,它關注於如何更靈活、更有效地創建物件。這些模式可以隱藏創建物件的細節,並提供創建物件的機制,例如單例模式、工廠模式… 等等,詳細解說請點擊以下連結
● Singleton 單例模式 | 解說實現 | Android Framework Context Service
● Abstract Factory 設計模式 | 實現解說 | Android MediaPlayer
● Factory 工廠方法模式 | 解說實現 | Java 集合設計
● Builder 建構者模式 | 實現與解說 | Android Framwrok Dialog 視窗
● Clone 原型模式 | 解說實現 | Android Framework Intent
行為模式 Behavioral Patterns
● 行為模式 PK
● 行為模式 - Behavioral Patterns
:
行為模式關注物件之間的「通信」和「職責分配」。它們描述了一系列物件如何協作,以完成特定任務。這些模式專注於改進物件之間的通信,從而提高系統的靈活性。例如,策略模式、觀察者模式… 等等,詳細解說請點擊以下連結
● Stragety 策略模式 | 解說實現 | Android Framework 動畫
● Interpreter 解譯器模式 | 解說實現 | Android Framework PackageManagerService
● Chain 責任鏈模式 | 解說實現 | Android Framework View 事件傳遞
● Specification 規格模式 | 解說實現 | Query 語句實做
● Command 命令、Servant 雇工模式 | 實現與解說 | 物件導向設計
● Memo 備忘錄模式 | 實現與解說 | Android Framwrok Activity 保存
● Visitor 設計模式 | 實現與解說 | 物件導向設計
● Template 設計模式 | 實現與解說 | 物件導向設計
● Mediator 模式設計 | 實現與解說 | 物件導向設計
● Composite 組合模式 | 實現與解說 | 物件導向設計
● Observer 觀察者模式 | JDK Observer | Android Framework Listview
結構模式 Structural Patterns
● 結構模式 PK
● 結構模式 - Structural Patterns
:
結構模式專注於「物件之間的組成」,以形成更大的結構。這些模式可以幫助你確保當系統進行擴展或修改時,不會破壞其整體結構。例如,外觀模式、代理模式… 等等,詳細解說請點擊以下連結
● Decorate 裝飾模式 | 解說實現 | 物件導向設計
● Iterator 迭代設計 | 解說實現 | 物件導向設計