在 Java 并发编程中,synchronized(内置锁)和Lock(以 ReentrantLock 为核心的显式锁)是实现线程同步的两大核心手段。本文从底层实现、核心机制、性能特性、使用场景等维度,对两者进行全方位拆解,帮你彻底搞懂其原理与应用逻辑。
关键概念速释
| Monitor | 每个 Java 对象自带的监视器锁,是 synchronized 的底层载体 | 管理 synchronized 的锁获取 / 释放 / 阻塞 |
| 锁升级 | JVM 对 synchronized 的性能优化,锁状态不可逆升级 | 降低 synchronized 在低并发场景的开销 |
| AQS | 抽象队列同步器,是 Lock 体系的 “骨架” | 封装通用同步逻辑(排队、阻塞、唤醒) |
| state | AQS 的 volatile int 变量,表征锁 / 资源状态 | 标记锁是否被占用、重入次数等 |
| CLH 队列 | AQS 的双向链表队列 | 存放抢锁失败的线程,保证 FIFO 排队 |
| CAS | CPU 级原子指令(比较并交换) | 保证 state / 队列操作的原子性,无锁并发核心 |
一、核心认知:两者的本质定位
- synchronized:JVM 层面的内置同步机制,语法简洁、自动管理,无需手动操作,是 Java 开发者最基础的同步工具;
- Lock(核心为 ReentrantLock):JDK 层面的显式锁框架,基于 AQS 实现,功能灵活、可定制化强,是复杂并发场景的首选。
无论是 synchronized 还是 Lock,底层都围绕俩个核心逻辑设计:
两者的核心目标一致 ——保证多线程下共享资源的原子性、可见性、有序性,但实现层面、优化策略、功能边界完全不同。
二、synchronized 核心原理(JVM 内置的 “傻瓜式” 锁)
1. 底层基石:Monitor(监视器锁)
synchronized 的核心是对象的 Monitor(监视器) —— 每个 Java 对象都自带一个 Monitor,同步的本质是竞争 Monitor 的所有权。
Monitor 的 “占有 – 释放” 逻辑
synchronized 的本质是竞争 Monitor 的所有权,所有同步逻辑围绕 Monitor 展开:
- 获取锁(monitorenter 指令):
- 若 Monitor 进入数为 0,线程获取 Monitor 并将进入数设为 1,成为 Monitor 所有者;
- 若当前线程已持有该 Monitor(重入),进入数 + 1;
- 若 Monitor 被其他线程持有,当前线程进入阻塞状态,直到 Monitor 被释放。
- 释放锁(monitorexit 指令):
- 执行该指令的线程必须是 Monitor 所有者;
- 进入数 – 1,若减至 0 则释放 Monitor,唤醒阻塞队列中的线程重新竞争。
注:monitorexit 会在两种场景执行 —— 同步代码块正常执行完毕、代码块抛出异常时,保证锁一定会释放,避免死锁。
2. 锁升级(从 “低效” 到 “适配场景”):锁升级机制(JVM 的关键优化)
早期 synchronized 是 “重量级锁”,性能较差;JDK 1.6 后引入锁升级(不可逆),大幅提升性能,锁状态从低到高分为 4 级:
| 无锁 | 无线程竞争 | 直接操作数据,无加锁开销 | 无任何性能损耗 |
| 偏向锁 | 单线程重复获取锁 | 锁偏向第一个获取的线程,对象头标记 “偏向线程 ID”,后续该线程拿锁无需 CAS | 几乎无开销,单线程最优 |
| 轻量级锁 | 多线程交替获取锁(无持续竞争) | 用 CAS 操作抢锁(修改对象头的锁记录指向当前线程),失败则膨胀为重量级锁 | 低开销,避免内核态阻塞 |
| 重量级锁 | 多线程持续竞争锁 | 调用操作系统 Mutex(互斥锁),线程进入内核态阻塞 | 开销最大,依赖操作系统调度 |
- 偏向锁:解决 “单线程重复加锁” 的性能问题,避免每次加锁都走 CAS;
- 轻量级锁:解决 “少量线程交替竞争” 的问题,用用户态 CAS 替代内核态阻塞;
- 重量级锁:最终兜底方案,牺牲性能保证线程安全。
3. synchronized 核心特性
- 自动加锁 / 释放:无需手动操作,JVM 保证出同步块(或异常)时释放锁;
- 可重入:同一线程可多次获取同一把锁(Monitor 进入数累加);
- 非公平锁:默认不保证线程排队顺序,可能 “插队” 抢锁;
- 不可中断:线程抢锁失败会一直阻塞,无法被主动中断;
- 可见性:依赖 JVM 的内存屏障,保证锁释放后变量修改对其他线程可见。
三、Lock(ReentrantLock):JDK 层面的 “灵活式” 锁(以 ReentrantLock 为例)
Lock 是接口,ReentrantLock 是最核心的实现类,其底层完全依赖AQS(抽象队列同步器) ——AQS 是 Java 并发包的 “骨架”,ReentrantLock 只是 AQS 的 “业务实现”。
1. AQS(抽象队列同步器)
AQS 不是锁,而是一套同步模板框架,定义了 “线程抢锁 – 排队 – 唤醒” 的通用逻辑,核心组成:
- 同步状态(state):volatile int 变量,表示锁的状态(0 = 未锁定,>0 = 已锁定,值 = 重入次数);
- CLH 同步队列:双向链表,存放抢锁失败的线程,按 FIFO 排队;
- CAS 操作:CPU 级原子指令,保证 state 修改、队列操作的原子性;
- 独占 / 共享模式:ReentrantLock 用独占模式(同一时间仅一个线程持有锁)。
2. ReentrantLock 核心实现逻辑
(1)锁的获取(lock ())
tryAcquire () 是核心:分公平锁 / 非公平锁实现,本质是 CAS 修改 state。
(2)锁的释放(unlock ())
tryRelease ():state 减 1,只有减至 0 才真正释放锁,保证重入的正确性。
重入:同一线程拿锁时直接 state+1,释放时 state-1(和 synchronized 逻辑一致)。
3. 公平锁 vs 非公平锁(ReentrantLock 的核心差异)
ReentrantLock 默认是非公平锁,可通过构造函数指定公平锁:
(1)非公平锁(默认)
抢锁时直接 CAS 修改 state,不管队列中是否有等待线程,允许 “插队”:
protected final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 直接CAS抢锁,无视队列
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 重入:当前线程已持有锁,state+1
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
- 优势:吞吐量高(减少线程切换开销);
- 劣势:不公平,可能导致某些线程长期等待。
(2)公平锁
抢锁前先检查 CLH 队列,只有队列为空 / 当前线程是队头,才 CAS 抢锁:
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// hasQueuedPredecessors()检查队列是否有前驱线程
if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 重入逻辑和非公平锁一致
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
- 优势:公平,严格按排队顺序抢锁;
- 劣势:吞吐量低(频繁切换线程)。
4. ReentrantLock 核心特性(灵活度拉满)
- 手动加锁 / 释放:必须在 finally 中调用 unlock (),否则可能死锁;
- 可重入:和 synchronized 一致,state 记录重入次数;
- 可中断:支持 lockInterruptibly (),抢锁时可被中断;
- 超时抢锁:支持 tryLock (long timeout, TimeUnit unit),超时未抢到则返回;
- 条件变量:支持 Condition,实现线程的精准唤醒(synchronized 只能唤醒全部);
- 可见性:依赖 volatile state 和 CAS,保证变量修改的可见性。
四、synchronized vs ReentrantLock 核心对比
| 底层实现 | JVM 层面(Monitor + 锁升级) | JDK 层面(AQS+CAS+CLH 队列) |
| 锁类型 | 仅非公平锁 | 公平锁 / 非公平锁(可指定) |
| 释放方式 | 自动释放(出同步块 / 异常) | 手动释放(必须 unlock ()) |
| 中断支持 | 不支持 | 支持(lockInterruptibly ()) |
| 超时抢锁 | 不支持 | 支持(tryLock ()) |
| 精准唤醒 | 不支持(只能唤醒全部) | 支持(Condition) |
| 性能 | 低并发:和 ReentrantLock 持平;高并发:略差 | 高并发:性能更优 |
| 使用成本 | 低(语法简洁,不易出错) | 高(需手动释放,易遗漏) |
| 异常处理 | JVM 自动保证释放 | 需在 finally 中处理,否则死锁 |






