reentrantlock 可作为构建线程安全多级缓存的关键同步工具,通过 key 级细粒度锁、缓存段级锁和写操作独占锁分级控制并发,配合双重检查防止缓存穿透,支持可中断与超时机制,并可协同读写锁优化读多写少场景。

ReentrantLock 本身不直接实现多级缓存,但它可作为构建线程安全多级缓存(如本地缓存 + 分布式缓存)的关键同步工具。核心在于:用锁控制缓存加载、更新和失效的临界区,避免缓存击穿、雪崩或脏读。
按缓存层级粒度选择锁策略
多级缓存(例如 Caffeine 本地缓存 + Redis 远程缓存)中,并非所有操作都需要全局锁。应根据访问范围和一致性要求分级加锁:
-
Key 级细粒度锁:对每个缓存 key 维护一个独立的 ReentrantLock(如用 ConcurrentHashMap
存储),仅在 loadMissing(key) 或 refresh(key) 时锁定该 key,避免不同 key 间相互阻塞; - 缓存段级锁:若 key 数量极大或锁对象开销敏感,可按 hash 分段(如 key.hashCode() & 0x7F),每段共用一把锁,平衡并发与内存占用;
- 写操作独占锁:清除整个本地缓存或批量刷新时,才使用一把全局 ReentrantLock 保护元数据(如版本号、时间戳),读操作仍可无锁访问本地缓存。
防止缓存穿透与重复加载
当本地缓存未命中且远程缓存也为空时,多个线程可能同时回源查 DB 并写入缓存,造成无效压力。ReentrantLock 可配合双重检查实现“只加载一次”:
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
- 先查本地缓存,命中则直接返回;
- 未命中则尝试获取对应 key 的 ReentrantLock(建议用 tryLock 避免长等待);
- 获得锁后,再次检查本地缓存(因其他线程可能已加载完成),未命中再查远程缓存;
- 远程也为空,则查 DB 并写入两级缓存,最后释放锁;
- 未获取到锁的线程,可等待短暂时间后重试,或直接降级返回空/默认值。
支持可中断与超时的缓存加载
相比 synchronized,ReentrantLock 支持 lockInterruptibly() 和 tryLock(timeout),这对缓存加载场景很实用:
- 调用 lockInterruptibly() 可响应业务线程中断(如接口超时),避免锁等待拖垮请求链路;
- 用 tryLock(100, TimeUnit.MILLISECONDS) 限制等待时间,超时后可走降级逻辑(如返回旧值、空值或抛出特定异常);
- 务必在 finally 块中 unlock(),推荐用 try-finally 或 try-with-resources(配合自定义 AutoCloseable 锁包装器)确保释放。
与读写锁协同优化读多写少场景
若多级缓存以读为主(如配置中心、字典表),可改用 ReentrantReadWriteLock:
- 读操作用 readLock(),允许多个线程并发读本地缓存;
- 写操作(如刷新、失效)用 writeLock(),保证更新原子性;
- 注意:writeLock 不可升级为 readLock,且持有 writeLock 期间其他线程无法获取 readLock;
- 对于“读本地 → 未命中 → 加读锁 → 查远程 → 写本地”这类流程,需谨慎设计锁顺序,避免死锁(建议先获取写锁再查远程,或统一用写锁兜底)。
不复杂但容易忽略的是锁的作用域与生命周期管理——锁对象不能随缓存 entry 创建/销毁频繁新建,也不宜长期驻留内存;推荐结合 WeakReference 或定时清理机制管理锁实例。真正关键的不是锁本身,而是它所保护的数据边界是否与业务语义一致。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










