reentrantlock 与 synchronized 的本质差异在于 api 层 vs 语言层、手动调度 vs 自动托管:前者基于 aqs+unsafe.cas,锁状态存于实例字段;后者由 jvm 原生指令驱动,锁状态写入对象头 mark word,并具备自动锁升级机制。

ReentrantLock 和 synchronized 在 JVM 层面的锁实现差异,本质是“API 层 vs 语言层”、“手动调度 vs 自动托管”的分野。理解这点,才能避开误用、死锁和性能陷阱。
底层执行载体不同
synchronized 是 JVM 原生指令驱动的锁,编译后生成 monitorenter 和 monitorexit 字节码,由 JVM 解释器或 JIT 直接调用 C++ 实现的 ObjectMonitor;而 ReentrantLock 是纯 Java 类,所有逻辑都在 java.util.concurrent.locks 包中,依赖 AQS + Unsafe.CAS 实现,JVM 不感知其“锁语义”,只把它当作普通对象操作。
这意味着:
- synchronized 的加锁/解锁行为由 JVM 统一管控,无法绕过或定制;
- ReentrantLock 的 lock()/unlock() 只是普通方法调用,JVM 不做特殊处理,一切逻辑由 Java 代码控制。
锁状态存储位置不同
synchronized 的锁状态直接写入对象头的 Mark Word:无锁、偏向、轻量、重量四种状态全靠修改这 32 或 64 位字段完成;而 ReentrantLock 的锁状态(如 state 值、持有线程、等待队列)全部保存在 ReentrantLock 实例自身字段 + AQS 的 volatile int state + Node 链表 中,与被锁对象无关——它不修改任何对象头。
因此:
- 一个对象能否被 synchronized 锁住,取决于它是否是 Java 对象(有 Mark Word);
- ReentrantLock 可以锁任意逻辑资源,甚至跨多个对象,只要开发者能保证 unlock() 被正确调用。
锁升级机制是否存在
synchronized 具备 JVM 内置的自动锁升级流程:从偏向锁(单线程无竞争)→ 轻量级锁(CAS 自旋)→ 重量级锁(进入 Monitor 阻塞队列),整个过程透明、不可干预;而 ReentrantLock 没有锁升级概念,它始终基于 AQS 的 volatile state + CAS + park/unpark 运行,无论竞争高低,都是同一套机制——高竞争时直接走阻塞路径,不尝试自旋优化。
这也带来实际影响:
- 低竞争下,synchronized 偏向锁几乎零开销;
- ReentrantLock 即便只有一两个线程争抢,也会触发完整的 AQS 入队/唤醒流程。
内核态介入时机不同
两者最终都依赖操作系统原语(如 Linux 的 futex)实现线程挂起与唤醒,但触发时机不同:
- synchronized 在升级为重量级锁后,才调用 ObjectMonitor::EnterI() 进入内核态阻塞;
- ReentrantLock 在 tryAcquire 失败后,若需等待,立刻调用 LockSupport.park(),间接触发 futex 系统调用。
换句话说:synchronized 更“懒”,尽量在用户态兜底;ReentrantLock 更“果断”,一旦抢不到就交由系统调度——这也是它在高竞争下更稳定的原因之一。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











