前文講到自定義對象池的實現,通常來說都是獲取到對象,使用完之後要主動歸還對象。但是在某些場景下,並不能輕易在代碼中調用 returnObject
方法歸還。此時就需要 Case by Case
具體情況具體分析,解決了。
今天分享一下自定義對象池在本地高性能緩存框架 Caffeine
中的使用。從對象池中借出的對象會存放在 Caffeine
緩存當中,然後就需要依賴 Caffeine
自己的過期和資源回收策略,決定何時回收對象。
Caffeine
框架提供了一個 API
用於處理對象過期或者被淘汰時業務邏輯,就是 RemovalListener
。RemovalListener
可以監視緩存中的條目移除,並在移除時執行自定義的邏輯。
下面是使用案例:
public static void main(String[] args) {
// 創建一個帶有RemovalListener的緩存
Cache<String, String> cache = Caffeine.newBuilder()
.maximumSize(100)
.removalListener(new MyRemovalListener())
.build();
// 將鍵值對放入緩存
cache.put("tester1", "FunTester001");
cache.put("tester2", "FunTester002");
// 從緩存中移除一個條目
cache.invalidate("key1");
}
// 自定義的RemovalListener實現
static class MyRemovalListener implements RemovalListener<String, String> {
@Override
public void onRemoval(String key, String value, RemovalCause cause) {
System.out.println("Key: " + key + ", Value: " + value + " has been removed from the cache.");
}
}
但是在我在業務中使用下面的代碼時,卻發生意外的情況。
Caffeine.newBuilder()
.expireAfterWrite(1, TimeUnit.MINUTES)
.build()
當對象過期以後,並沒有出發 RemovalListener
執行。所以我查詢官方資源,Caffeine
的回收策略,有三種:
- 定時清理:在固定的時間間隔內執行清理操作。
- 延遲清理:在緩存中的條目過期後一定時間內執行清理操作。
- 手動清理:在特定的事件觸發時手動執行清理操作。
我們需要手動指定 Caffeine
回收策略,也就是調度器 schedule
。它負責在緩存中管理定期清理過期條目的操作。Caffeine允許你自定義調度器的實現,以便更靈活地控制緩存的行爲。
在Caffeine中,有四種不同類型的調度器(Scheduler)可供選擇,它們分別是:SystemScheduler
、GuardedScheduler
、DisabledScheduler
和ExecutorServiceScheduler
。下面我將解釋它們之間的差異:
- SystemScheduler:
SystemScheduler
是Caffeine的默認調度器。- 它使用系統級的調度機制來執行定期清理任務。
- 這種調度器適用於大多數場景,並且通常表現良好。
- GuardedScheduler:
GuardedScheduler
是一個具有保護機制的調度器。- 它可以確保任務不會重複執行,即使調度器被多次觸發。
- 這種調度器適用於需要保證任務不會被重複執行的場景。
- DisabledScheduler:
DisabledScheduler
是一個禁用調度器,它不會執行任何清理任務。- 當你不希望緩存自動執行清理操作時,可以使用這個調度器。
- 這種調度器適用於不需要自動清理的場景,或者你希望手動控制清理的時機。
- ExecutorServiceScheduler:
ExecutorServiceScheduler
是一個基於ScheduledExecutorService
的調度器。- 它使用
ScheduledExecutorService
來執行定期清理任務。 - 這種調度器適用於需要更精細控制清理任務的執行方式,比如使用自定義的線程池、調整執行頻率等。
這些調度器提供了不同的選擇,以滿足不同的需求和場景。你可以根據自己的需求來選擇適合的調度器類型,以確保緩存的清理操作能夠按照預期的方式執行。
那麼問題來了,Caffeine
默認的調度器是那個呢?爲什麼沒有及時回收掉過期的資源呢?
在 Caffeine
源碼中,我得到了答案,位於 com.github.benmanes.caffeine.cache.Caffeine#getScheduler
,內容如下:
Scheduler getScheduler() {
if (this.scheduler != null && this.scheduler != Scheduler.disabledScheduler()) {
return this.scheduler == Scheduler.systemScheduler() ? this.scheduler : Scheduler.guardedScheduler(this.scheduler);
} else {
return Scheduler.disabledScheduler();
}
}
默認的是 disabledScheduler()
,顧名思義,看起來是關閉了調度器的功能。下一步我們看看實際代碼 com.github.benmanes.caffeine.cache.DisabledScheduler
內容:
package com.github.benmanes.caffeine.cache;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
enum DisabledScheduler implements Scheduler {
INSTANCE;
private DisabledScheduler() {
}
public Future<Void> schedule(Executor executor, Runnable command, long delay, TimeUnit unit) {
Objects.requireNonNull(executor);
Objects.requireNonNull(command);
Objects.requireNonNull(unit);
return DisabledFuture.INSTANCE;
}
}
我們看看 schedule()
方法,除了驗證了一下參數意外,好像什麼都沒做啊,事實上就是什麼都沒做。實際上依靠 Caffeine
的一些驅逐策略完成的回收,也就是當我們訪問過期資源、手動置無效、手動調用清理資源方法才能觸發。
作爲對比,我們看一下另外一個調度器的實現 com.github.benmanes.caffeine.cache.SystemScheduler
:
package com.github.benmanes.caffeine.cache;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
enum SystemScheduler implements Scheduler {
INSTANCE;
private SystemScheduler() {
}
public Future<?> schedule(Executor executor, Runnable command, long delay, TimeUnit unit) {
Executor delayedExecutor = CompletableFuture.delayedExecutor(delay, unit, executor);
return CompletableFuture.runAsync(command, delayedExecutor);
}
}
看到是使用了線程池實現異步功能,其中涉及到了一個具有延遲涉及的線程池,繞的有點遠了,這裏不再詳細說明。
解決辦法也有了,就是設置有用的調度器,我選擇了 com.github.benmanes.caffeine.cache.SystemScheduler
,實測也是足夠滿足需求的。代碼如下:
static void main(String[] args) {
def pool = new FunPool<String>(new FunPooledFactory<String>() {// 創建對象池
@Override
String newInstance() {
return "FunTester" + getRandomInt(Integer.MAX_VALUE)// 創建對象
}
})
def listener = new RemovalListener<String, String>() {
@Override
void onRemoval(String key, String value, RemovalCause removalCause) {
output("Key: $key , Value: $value cause: $removalCause")// 打印移除原因
pool.back(value)// 回收對象
}
}
def build = Caffeine<String, String>.newBuilder()
.expireAfterWrite(1, TimeUnit.SECONDS)// 設置寫入後過期時間
.maximumSize(100) // 設置緩存的最大容量
.removalListener(listener) // 設置緩存移除監聽器
.scheduler(Scheduler.systemScheduler())
.build()
build.put("tester", pool.borrow())// 放入緩存
sleep(2.0)// 等待緩存過期
}
控制檯輸出:
12:29:15:054 ForkJoinPool.commonPool-worker-2 Key: tester , Value: FunTester208146637 cause: EXPIRED
成功觸發 RemovalListener
,且將對象歸還給對象池。