synchronized是粗粒度全互斥锁,所有读写操作均串行化;reentrantreadwritelock则按读写语义分离,允许多个读线程并发、读写/写写互斥,显著提升读多写少场景的吞吐量。
synchronized 和 reentrantreadwritelock 的核心差异不在“能不能互斥”,而在于“互斥的粒度和逻辑不同”:前者是粗粒度全互斥,后者按读写语义精细化分离。
读操作是否允许并发
synchronized 对所有访问(无论读或写)都强制串行化。哪怕只是 get() 一个值,也会阻塞其他线程的任何访问——包括另一个纯读请求。
ReentrantReadWriteLock 的 readLock() 是共享锁。多个线程可同时持有读锁,只要没有线程持有写锁。这在缓存读取、配置查询等高频只读场景下显著提升吞吐量。
- 典型场景:100 个线程并发读取 HashMap 数据 → synchronized 需排队执行;readLock() 可全部并行
- 注意:读锁不排斥读锁,但排斥写锁;写锁排斥一切(读/写)
写操作的互斥强度相同,但行为逻辑不同
两者对写操作都保证排他性:同一时刻最多一个线程能执行写逻辑。但关键区别在于“写操作发生时,读操作如何响应”:
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
- synchronized 下,写线程进入同步块后,后续所有读请求(无论是否已排队)必须等待该写完成
- ReentrantReadWriteLock 中,一旦有线程申请写锁,新来的读请求会被阻塞;已获取读锁的线程可继续执行,直到全部释放后,写锁才被授予
- 默认策略是非公平的,但可配置为写优先(避免读饥饿),也可通过 tryLock 控制等待行为
锁升级与资源开销
synchronized 在 JDK 1.6+ 后具备锁升级机制(偏向→轻量→重量),低竞争时开销极小,且由 JVM 自动管理生命周期。
ReentrantReadWriteLock 是显式锁,需手动 lock/unlock,且内部维护两把锁(ReadLock 和 WriteLock)及状态协调逻辑,内存占用和 CPU 调度开销略高。但它不依赖对象头 Mark Word,适用于更复杂的锁协作场景(如多条件等待、分段读写控制)。
- 简单临界区保护(如计数器增减)→ synchronized 更轻量、更安全
- 读远多于写的共享数据结构(如 ConcurrentMap 的底层实现、本地缓存)→ ReadWriteLock 更高效
可见性与内存语义一致
二者都遵循 JMM 的 happens-before 规则:锁释放前的写操作对后续获取同一锁的线程可见。因此在正确使用前提下,都能保证变量修改的可见性,无需额外加 volatile。
- synchronized 释放 monitor 时刷新主内存;readLock()/writeLock() 释放时同样插入内存屏障
- 不正确使用(如忘记 unlock、未在 finally 块中释放)会导致死锁或可见性失效,但这是使用问题,非机制缺陷
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










