ScheduledExecutorService 实现缓存定时失效刷新的核心是定期主动触发异步刷新任务,分离缓存有效期与刷新周期,用 AtomicReference 或 ConcurrentHashMap 原子更新值,支持时间戳与过期标记,结合主动刷新与懒刷新,并需优雅关闭调度器。

用 ScheduledExecutorService 实现缓存的定时失效刷新,核心是“定期触发刷新任务”,而不是等缓存真正过期才加载——这样能避免请求击穿和延迟毛刺。关键在于分离“缓存有效期”和“刷新周期”,让新值提前加载、无缝替换。
缓存结构要支持原子更新
不能直接替换整个缓存对象(可能引发读取空指针或不一致),推荐用 AtomicReference 或线程安全容器封装缓存值:
- 用
AtomicReference<CachedData>存当前有效值,刷新时用compareAndSet原子更新 - 或使用
ConcurrentHashMap<String, CompletableFuture<Object>>管理异步加载状态,避免重复刷新 - 缓存实体建议自带时间戳(如
lastRefreshTime)和是否“已过期但尚未刷新”的标记
刷新任务要异步且非阻塞
刷新逻辑必须独立于用户请求线程,否则拖慢响应;同时要防止并发刷新同一 key:
客服回复模板。售前咨询、售后处理、退换货、投诉回复、好评引导、升级处理、行业FAQ、满意度挽回。Customer service reply templates for pre-sale, after-sale, returns, complaints, escalation, FAQ generation, s...
- 每次调度只提交一个
Runnable或Callable,内部做实际加载 + 原子更新 - 加载失败时保留旧值,并记录日志,避免缓存雪崩
- 示例:用
scheduler.scheduleAtFixedRate(refreshTask, 0, 30, TimeUnit.SECONDS)每30秒刷新一次
区分“被动过期”和“主动刷新”
缓存对外仍可设 TTL(比如 60 秒),但刷新周期(比如 45 秒)更短,确保在过期前完成更新:
立即学习“Java免费学习笔记(深入)”;
- 读取时先检查是否“已过期”,若过期则返回旧值并触发异步刷新(即“懒刷新”)
- 定时任务则是“主动刷新”,不管当前值是否过期,到点就加载新数据
- 两者结合更健壮:主动刷新保时效,被动兜底防单点故障
关闭时要优雅释放资源
ScheduledExecutorService 不会自动关闭,必须显式处理生命周期:
- 应用关闭前调用
scheduler.shutdown(),再配合awaitTermination()等待任务结束 - 必要时用
scheduler.shutdownNow()强制中断,但需确保刷新任务支持中断(如检查Thread.interrupted()) - 避免内存泄漏:每个缓存 key 对应的调度任务,若动态增删 key,要用
ScheduledFuture.cancel()及时清理

















