如何节流Guava Loading Cache的刷新请求?等待超时后执行刷新
解决Guava Loading Cache的刷新防抖需求
嘿,这个需求其实是典型的**防抖(debounce)**场景,和Guava RateLimiter解决的限流问题完全不是一回事——RateLimiter是控制单位时间内允许通过的请求数,而你要的是把短时间内的多次刷新请求合并成一次执行,只有当1秒内没有新请求时才真正触发缓存刷新。
Guava本身并没有内置的防抖工具类,但你可以用它提供的并发工具快速搭建一个可靠的实现,不用从零开始写所有逻辑。
核心实现思路
- 用原子引用保存当前待执行的刷新任务,确保多线程下的安全操作
- 每次收到刷新请求时,先取消之前待执行的任务,再重新安排一个1秒后执行的新任务
- 用Guava的
MoreExecutors管理调度线程池,避免手动处理线程生命周期的麻烦
具体代码实现
import com.google.common.util.concurrent.MoreExecutors; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; public class DebouncedCacheRefresher { // 用Guava工具类创建自动退出的调度线程池,避免资源泄漏 private final ScheduledExecutorService scheduler = MoreExecutors.getExitingScheduledExecutorService( (ScheduledExecutorService) Executors.newSingleThreadScheduledExecutor()); // 原子引用保存当前待执行的刷新任务,保证线程安全 private final AtomicReference<ScheduledFuture<?>> pendingRefresh = new AtomicReference<>(); // 实际的缓存刷新逻辑 private final Runnable refreshTask; // 防抖延迟时间(这里设置为你需要的1秒) private final long debounceDelayMillis; public DebouncedCacheRefresher(Runnable refreshTask, long debounceDelayMillis) { this.refreshTask = refreshTask; this.debounceDelayMillis = debounceDelayMillis; } public void requestRefresh() { // 取出并清除之前的待执行任务 ScheduledFuture<?> previousTask = pendingRefresh.getAndSet(null); // 如果任务还未执行,取消它 if (previousTask != null && !previousTask.isDone()) { previousTask.cancel(false); } // 安排新的刷新任务 ScheduledFuture<?> newTask = scheduler.schedule(refreshTask, debounceDelayMillis, TimeUnit.MILLISECONDS); // 尝试将新任务存入原子引用,若失败则取消新任务(避免并发冲突) if (!pendingRefresh.compareAndSet(null, newTask)) { newTask.cancel(false); } } }
使用方式
- 把你的缓存刷新逻辑封装成
Runnable,比如针对某个LoadingCache:
LoadingCache<String, Object> myCache = // 你的缓存实例 Runnable refreshLogic = () -> { // 这里写实际的刷新逻辑,比如: myCache.invalidateAll(); // 若需要主动加载缓存,可调用myCache.refreshAll()(注意LoadingCache的refresh方法是异步的,按需调整) };
- 创建防抖刷新器实例,设置1秒延迟:
DebouncedCacheRefresher refresher = new DebouncedCacheRefresher(refreshLogic, 1000);
- 所有需要触发缓存刷新的地方,统一调用
refresher.requestRefresh()即可:
// 多处调用该方法,短时间内的请求会被自动合并 refresher.requestRefresh();
注意事项
- 如果应用中有多个需要防抖刷新的缓存,可为每个缓存单独创建
DebouncedCacheRefresher实例 MoreExecutors.getExitingScheduledExecutorService会在JVM退出时自动关闭线程池;若需手动关闭,可调用scheduler.shutdown()- 若后续需要节流(throttle)(固定间隔执行,不受请求频率影响),逻辑会略有不同,但当前实现完全匹配你的防抖需求
内容的提问来源于stack exchange,提问作者Christian Kellner
相关产品推荐
相关产品推荐

