本地缓存封装核心是收束并发控制、过期判断与淘汰逻辑:读写自动加锁(读锁允许多线程并发,写锁保证原子性),过期检查贯穿get/put全流程并原子移除,用nanotime防时钟回拨,lru借助linkedhashmap回调实现o(1)驱逐,懒惰淘汰与定时清理协同保障内存与性能。

封装本地缓存,核心是把读写并发控制、过期判断、淘汰逻辑全部收束在内部,让调用方只需写 cache.get(key) 或 cache.put(key, value),不感知锁、不处理时间校验、不手动清理。
读写操作自动加锁,不暴露锁接口
get 方法内部自动获取读锁(readLock),允许多线程并发读;put 和 remove 使用写锁(writeLock),保证写入原子性。所有锁操作必须包裹在 try/finally 中,确保异常时也能释放。不提供 lock/unlock 等手动加锁方法,避免业务代码误用锁粒度或忘记释放。
过期检查贯穿整个生命周期,不是“查时才判”
每次 get 返回前都检查是否过期,过期则立即移除并返回 null;每次 put 后触发容量与过期双重检查,必要时按 LRU 或 LFU 淘汰;clean() 方法供定时任务主动扫描,但日常读写已自带轻量清理,不依赖外部调用。
- 不暴露 expirationTime 字段本身,防止绕过判断逻辑
- 用 System.nanoTime() 记录相对过期时间,规避系统时钟回拨导致的误淘汰
- 过期校验和 remove 必须原子执行:先从链表断开,再从哈希表移除,顺序不可颠倒
淘汰策略与容量控制不拖慢写入性能
LRU 的链表维护不能在每次 put 后遍历全量节点——那样是 O(n) 复杂度。应利用 LinkedHashMap 的 afterNodeInsertion 回调,在插入后由框架自动触发容量检查和尾部驱逐。
- 驱逐动作需同步更新链表与哈希表两处结构,否则出现“查不到但链表还连着”的脏状态
- 不建议在驱逐时同步执行 onEvict 回调,可能阻塞主线程;应异步投递到线程池
- 对访问极不均衡的数据(如少数爆款),优先用 LFU;对有时间局部性的数据(如用户最近浏览),LRU 更合适
组合懒惰淘汰 + 定期清理,兼顾内存与 CPU
懒惰淘汰(get 时检查)降低 CPU 开销,但会导致长期未访问的过期项滞留;定期清理(ScheduledExecutorService 扫描)保障内存及时释放,但高频扫描加重负担。两者必须共存:
- 定时任务间隔不宜过短(如 30 秒~2 分钟),避免频繁遍历
- 扫描只清理明显过期项,不做强制全量淘汰
- 清理逻辑复用 get 中的过期判断机制,保持行为一致
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











