synchronized实现本地缓存读写互斥的核心是使用私有final锁对象确保所有临界操作串行化:写操作(put/remove/clear)必须同步,读-写复合操作(getOrLoad)必须加锁,普通get可无锁以提升并发读性能,且synchronized天然可重入、异常时自动释放锁。

用 synchronized 实现本地缓存的读写互斥,核心是让所有对缓存的修改(写)和关键读操作(如缓存未命中时的加载)串行化,避免脏读、重复加载或覆盖。它不追求极致性能,但简单可靠,适合中低并发场景。
明确锁对象:别用 this 或 Class,推荐专用锁对象
本地缓存通常是单例或静态工具类,若直接用 synchronized 修饰方法或写 synchronized(this),容易造成锁粒度太大或锁对象不一致。更稳妥的做法是定义一个私有 final 锁对象:
-
不要写
synchronized public Cache get(String key)—— 静态方法锁的是 Class,实例方法锁的是 this,混用易出错 -
推荐声明
private final Object lock = new Object();,所有读写操作都围绕它加锁 - 这样能确保“同一缓存实例”内的所有临界操作受同一把锁保护,且不会被外部干扰
写操作必须同步:put、remove、clear 全部加锁
任何改变缓存内容的操作都需排他执行,防止多个线程同时更新导致数据丢失或结构不一致:
public void put(String key, Object value) { synchronized(lock) { cacheMap.put(key, value); } }public void remove(String key) { synchronized(lock) { cacheMap.remove(key); } }public void clear() { synchronized(lock) { cacheMap.clear(); } }
读操作按需同步:普通 get 可不锁,get-or-load 必须锁
纯读取(get)本身不改变状态,通常可不加锁以提升并发读性能;但“先查后写”的复合操作(比如缓存未命中时从数据库加载并写入)必须整体加锁,否则会引发经典“缓存击穿”或重复加载问题:
立即学习“Java免费学习笔记(深入)”;
-
安全的读:
public Object get(String key) { return cacheMap.get(key); }—— 无锁,依赖 HashMap 的弱一致性(读到旧值一般可接受) -
必须加锁的读-写:
public Object getOrLoad(String key, Supplier<Object> loader) { synchronized(lock) { Object v = cacheMap.get(key); if (v == null) { v = loader.get(); cacheMap.put(key, v); } return v; } }
注意可重入与异常安全
synchronized 天然可重入,同一个线程多次进入同一把锁不会死锁;但它不自动处理异常——如果锁内抛出异常,锁仍会正常释放(JVM 保证 monitorexit 在方法退出或异常时执行),所以无需 try-finally 手动解锁,但业务逻辑仍需自己捕获异常、避免半截写入:
- 例如在
getOrLoad中,若loader.get()抛异常,cacheMap不会被修改,符合预期 - 但如果加载成功后
put前发生 OOM,实际已无法干预,此时靠锁机制本身已足够


















