深入理解Java中的synchronized 和 ReentrantLock 底层原理
作者:北郭guo
synchronized 和 ReentrantLock 是 Java 中最核心的两种锁实现,前者是 JVM 内置锁(隐式锁),后者是 JUC 显式锁(基于 AQS 框架)。两者的实现底层依赖完全不同,但核心目标一致 —— 通过「互斥」保证并发安全,下面从 底层依赖、核心流程、关键机制 三个维度,深入拆解它们的实现原理:
一、synchronized的实现:JVM 层面的 “自动锁”
synchronized 是 Java 语言原生支持的锁,无需手动释放,底层由 JVM 解释器 + 操作系统内核 协同实现,核心依赖 对象头(Mark Word) 和 监视器(Monitor),并通过「锁升级」机制优化性能。
1. 核心依赖组件
(1)对象头(Mark Word):锁状态的 “存储载体”
Java 中所有对象都能作为锁,本质是因为对象在内存中的「对象头」包含了锁相关的状态信息。对象头由两部分组成:Mark Word(核心)和 Klass Pointer(指向类元数据)。
Mark Word 是一个动态结构(32 位 JVM 示例),会根据锁状态变化存储不同信息,核心字段包括:
| 锁状态 | Mark Word 结构(32 位) | 核心作用 |
|---|---|---|
| 无锁状态 | 哈希码(25 位) + 分代年龄(4 位) + 锁标志位(01) | 存储对象哈希码、GC 分代信息,无锁标识 |
| 偏向锁 | 线程 ID(23 位) + Epoch(2 位) + 分代年龄(4 位) + 锁标志位(01) | 记录持有锁的线程 ID,实现 “无竞争时零开销” |
| 轻量级锁 | 指向栈帧中 “锁记录” 的指针(30 位) + 锁标志位(00) | 关联线程栈中的锁记录,实现自旋锁机制 |
| 重量级锁 | 指向 Monitor 的指针(30 位) + 锁标志位(10) | 关联操作系统级别的 Monitor,实现阻塞同步 |
👉 关键:Mark Word 的「锁标志位」决定当前锁状态,JVM 通过修改 Mark Word 实现锁的切换。
(2)监视器(Monitor):重量级锁的 “核心同步工具”
Monitor 是操作系统层面的「互斥同步原语」(也叫管程),是 synchronized 重量级锁的核心依赖。每个 Java 对象在创建时,JVM 会为其关联一个 Monitor(可理解为 “锁对象”),Monitor 内部包含三个核心结构:
- 所有者(Owner):存储当前持有锁的线程(唯一,保证互斥);
- EntryList(阻塞队列):存储等待获取锁的线程(竞争失败后进入阻塞状态);
- WaitSet(等待队列):存储调用
wait()方法后释放锁的线程(需被notify()唤醒)。
👉 本质:Monitor 通过操作系统的「互斥量(Mutex)」实现线程阻塞 / 唤醒,是重量级锁性能开销的主要来源。
2.synchronized的核心实现流程(锁升级)
synchronized 并非一开始就是重量级锁,JVM 会根据「竞争激烈程度」自动触发「锁升级」(无锁→偏向锁→轻量级锁→重量级锁),目的是在 “无竞争” 和 “激烈竞争” 场景下都能保证性能。
(1)无锁 → 偏向锁(单线程竞争)
- 触发条件:只有一个线程尝试获取锁。
- 实现流程:
- 线程 T1 第一次进入同步块时,JVM 通过 CAS 操作 修改对象的
Mark Word:将「线程 ID」写入Mark Word,锁标志位保持 01(偏向锁标识); - 后续 T1 再次进入同步块时,只需判断
Mark Word中的线程 ID 是否为自己:- 是:直接进入(无需任何同步操作,零开销);
- 否:偏向锁失效,触发升级。
- 线程 T1 第一次进入同步块时,JVM 通过 CAS 操作 修改对象的
- 核心目的:优化 “单线程重复获取锁” 的场景(如单线程操作集合),避免无意义的同步开销。
(2)偏向锁 → 轻量级锁(低并发交替竞争)
- 触发条件:有第二个线程 T2 尝试获取同一把锁(T1 未释放)。
- 实现流程:
- JVM 先暂停 T1,撤销偏向锁(将
Mark Word恢复为无锁状态); - T1 和 T2 各自在自己的「栈帧」中创建一个「锁记录(Lock Record)」,锁记录中存储对象
Mark Word的副本(Displaced Mark Word); - 两个线程通过 CAS 操作 竞争修改对象的
Mark Word:将Mark Word指向自己的锁记录;- 成功:获取轻量级锁,进入同步块;
- 失败:说明竞争存在,进入自旋重试(循环尝试 CAS,避免阻塞)。
- JVM 先暂停 T1,撤销偏向锁(将
- 核心目的:优化 “多线程交替执行” 的场景(无激烈竞争),通过自旋减少线程阻塞 / 唤醒的开销(用户态操作,比内核态高效)。
(3)轻量级锁 → 重量级锁(高并发激烈竞争)
- 触发条件:自旋次数超过阈值(JDK1.6 后为「自适应自旋」,根据前一次自旋成功率动态调整),或有更多线程参与竞争。
- 实现流程:
- 自旋失败的线程放弃竞争,将锁升级为重量级锁(修改
Mark Word指向 Monitor); - 线程进入 Monitor 的
EntryList队列,放弃 CPU 资源(进入 阻塞状态,由操作系统调度); - 持有锁的线程释放锁时,通过操作系统唤醒
EntryList中的一个线程,该线程再次尝试获取锁。
- 自旋失败的线程放弃竞争,将锁升级为重量级锁(修改
- 核心目的:应对 “高并发激烈竞争” 场景,通过阻塞机制避免 CPU 空转(自旋会消耗 CPU)。
3. 解锁与自动释放机制
synchronized 无需手动解锁,JVM 会在以下场景自动释放锁:
- 线程退出同步块(
synchronized代码块或方法); - 线程抛出异常时(JVM 会在异常处理中插入解锁指令)。
解锁的核心操作:
- 轻量级锁:通过 CAS 将
Mark Word恢复为「Displaced Mark Word」(锁记录中的副本); - 重量级锁:将 Monitor 的「Owner」设为 null,唤醒
EntryList中的线程。
4. 内存语义的实现
synchronized 除了互斥,还能保证可见性和有序性,底层通过 内存屏障(Memory Barrier) 实现:
- 进入同步块时:插入「Load Barrier」,清空工作内存,从主内存加载最新共享变量(保证可见性);
- 退出同步块时:插入「Store Barrier」,将工作内存中的修改刷新到主内存(保证可见性);
- 同步块内:禁止指令重排序(保证有序性)。
二、ReentrantLock 的实现:JUC 层面的 “手动锁”
ReentrantLock 是 java.util.concurrent.locks 包下的显式锁,基于 AQS(AbstractQueuedSynchronizer,抽象队列同步器) 实现,支持公平锁 / 非公平锁、可中断、条件锁等灵活特性,完全由 Java 代码实现(无需 JVM 介入)。
1. 核心依赖组件:AQS(锁的 “骨架”)
AQS 是 JUC 同步工具的基础(如 ReentrantLock、Semaphore、CountDownLatch),其核心设计是「状态变量 + 同步队列」,所有锁的逻辑都围绕 AQS 扩展。
(1)AQS 的核心结构
- 状态变量(state):
volatile int state,存储锁的状态:state = 0:锁未被持有;state > 0:锁已被持有,值等于「重入次数」(支持可重入);
- 同步队列(CLH 队列):双向链表,每个节点是
Node对象,存储等待锁的线程、等待状态(如CANCELLED、WAITING)等; - 独占线程(exclusiveOwnerThread):存储当前持有锁的线程(仅用于独占锁,
ReentrantLock是独占锁)。
(2)AQS 的核心模板方法
AQS 定义了一套「模板方法」,子类(如 ReentrantLock 的内部类 Sync)只需实现以下抽象方法,即可完成锁的逻辑:
tryAcquire(int arg):尝试获取锁(arg 为获取锁的次数,重入时 arg=1);tryRelease(int arg):尝试释放锁(arg 为释放锁的次数,重入时 arg=1);isHeldExclusively():判断当前线程是否持有锁(支持可重入)。
👉 本质:AQS 封装了 “队列管理”“线程阻塞 / 唤醒” 等通用逻辑,子类只需实现 “锁的获取 / 释放” 核心逻辑,遵循「模板方法设计模式」。
2. ReentrantLock 的内部结构
ReentrantLock 本身不直接实现 AQS,而是通过内部类 Sync 继承 AQS,再由 Sync 的两个子类实现「公平锁」和「非公平锁」:
public class ReentrantLock implements Lock {
// 核心同步器(继承 AQS)
private final Sync sync;
// 抽象内部类:继承 AQS,定义锁的基础逻辑
abstract static class Sync extends AbstractQueuedSynchronizer {
abstract void lock(); // 抽象方法,由公平/非公平锁实现
// ... 实现 tryRelease 等通用方法
}
// 非公平锁实现(默认)
static final class NonfairSync extends Sync {
@Override void lock() { /* 非公平锁加锁逻辑 */ }
@Override protected boolean tryAcquire(int arg) { /* 非公平尝试获取锁 */ }
}
// 公平锁实现
static final class FairSync extends Sync {
@Override void lock() { /* 公平锁加锁逻辑 */ }
@Override protected boolean tryAcquire(int arg) { /* 公平尝试获取锁 */ }
}
// 构造器:默认非公平锁
public ReentrantLock() { sync = new NonfairSync(); }
// 构造器:指定公平/非公平
public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
}3. ReentrantLock 的核心实现流程(以非公平锁为例)
(1)加锁流程(lock() 方法)
// NonfairSync 的 lock() 方法
@Override
public void lock() {
// 第一步:尝试 CAS 抢锁(state 从 0 改为 1)
if (compareAndSetState(0, 1)) {
// 抢锁成功,设置当前线程为独占线程
setExclusiveOwnerThread(Thread.currentThread());
} else {
// 抢锁失败,调用 AQS 的 acquire() 模板方法
acquire(1);
}
}
// AQS 的 acquire() 模板方法(通用逻辑)
public final void acquire(int arg) {
// 第二步:尝试获取锁(tryAcquire 由 NonfairSync 实现)
if (!tryAcquire(arg) &&
// 第三步:获取失败,将线程封装为 Node 加入同步队列
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) {
// 第四步:如果需要,中断当前线程(支持可中断)
selfInterrupt();
}
}
// NonfairSync 的 tryAcquire() 方法(核心抢锁逻辑)
@Override
protected boolean tryAcquire(int arg) {
return nonfairTryAcquire(arg);
}
// Sync 的 nonfairTryAcquire() 方法(非公平抢锁细节)
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState(); // 获取当前锁状态
// 情况 1:锁未被持有(state=0),再次尝试 CAS 抢锁
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 情况 2:锁已被当前线程持有(可重入),state 加 1
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) throw new Error("Maximum lock count exceeded");
setState(nextc); // 无需 CAS,当前线程已持有锁,线程安全
return true;
}
// 情况 3:锁被其他线程持有,抢锁失败
return false;
}加锁核心步骤总结:
- 线程先尝试「CAS 抢锁」(插队,非公平锁的核心);
- 抢锁成功:设置
state=1,标记当前线程为独占线程; - 抢锁失败:
- 若当前线程已持有锁(重入):
state += 1,直接成功; - 若锁被其他线程持有:将线程封装为
Node加入 AQS 同步队列,调用LockSupport.park()阻塞线程。
- 若当前线程已持有锁(重入):
(2)解锁流程(unlock() 方法)
// ReentrantLock 的 unlock() 方法
public void unlock() {
// 调用 Sync 的 release() 方法(AQS 模板方法)
sync.release(1);
}
// AQS 的 release() 模板方法(通用逻辑)
public final boolean release(int arg) {
// 第一步:尝试释放锁(tryRelease 由 Sync 实现)
if (tryRelease(arg)) {
// 第二步:释放成功,唤醒同步队列中的头节点线程
Node h = head;
if (h != null && h.waitStatus != 0) {
unparkSuccessor(h); // 唤醒后继节点(LockSupport.unpark())
}
return true;
}
return false;
}
// Sync 的 tryRelease() 方法(核心释放逻辑)
@Override
protected boolean tryRelease(int releases) {
int c = getState() - releases; // state 减 1(重入时多次减)
// 只有持有锁的线程能释放(否则抛异常)
if (Thread.currentThread() != getExclusiveOwnerThread()) {
throw new IllegalMonitorStateException();
}
boolean free = false;
// 情况 1:state 减为 0,完全释放锁
if (c == 0) {
free = true;
setExclusiveOwnerThread(null); // 清空独占线程
}
// 情况 2:state > 0(重入未完全释放),仅更新 state
setState(c);
return free;
}解锁核心步骤总结:
- 线程调用
unlock(),state减 1; - 若
state == 0:完全释放锁,清空独占线程,唤醒 AQS 同步队列中的后继线程; - 若
state > 0:仅更新state(重入锁未完全释放)。
4. 公平锁与非公平锁的实现差异
核心差异在 tryAcquire() 方法中,公平锁会多一步「判断队列是否有等待线程」:
// 公平锁 FairSync 的 tryAcquire() 方法
@Override
protected boolean tryAcquire(int arg) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 关键差异:先判断同步队列是否有前驱线程(hasQueuedPredecessors())
if (!hasQueuedPredecessors() && compareAndSetState(0, arg)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
// 重入逻辑与非公平锁一致
int nextc = c + arg;
if (nextc < 0) throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
// AQS 的 hasQueuedPredecessors() 方法:判断队列是否有等待线程(先到先得)
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
// 队列不为空,且当前线程不是队列的第一个等待线程 → 有前驱线程
return h != t && ((s = h.next) == null || s.thread != Thread.currentThread());
}- 非公平锁:抢锁时直接 CAS,不关心队列是否有等待线程(允许插队,性能高,但可能导致饥饿);
- 公平锁:抢锁前先检查队列是否有前驱线程,只有队列空或当前线程是第一个时才 CAS(先到先得,无饥饿,但队列操作有性能开销)。
5. 可中断与条件锁的实现
(1)可中断锁(lockInterruptibly())
ReentrantLock 支持线程在等待锁时被中断,底层通过 AQS 的 acquireInterruptibly() 实现:
- 线程在同步队列中阻塞时,若收到中断信号(
Thread.interrupt()),会抛出InterruptedException并退出等待; synchronized不支持可中断(线程一旦进入阻塞,必须等到锁释放或程序终止)。
(2)条件锁(Condition)
ReentrantLock 可通过 newCondition() 获取 Condition 对象,实现 “线程等待 / 唤醒” 的精细化控制(类似 Object.wait()/notify(),但支持多个等待队列):
Condition的底层是 AQS 的「等待队列」(每个Condition对应一个独立等待队列);- 调用
condition.await():线程释放锁,进入Condition的等待队列,阻塞; - 调用
condition.signal():唤醒Condition等待队列中的一个线程,使其加入 AQS 同步队列竞争锁。
三、synchronized与ReentrantLock实现对比
| 维度 | synchronized | ReentrantLock |
|---|---|---|
| 实现层面 | JVM 内置(C++ 实现,如 HotSpot 的 synchronizer.cpp) | Java 代码层面(基于 AQS 框架,纯 Java 实现) |
| 核心依赖 | 对象头(Mark Word) + Monitor(操作系统互斥量) | AQS(状态变量 + 同步队列) + CAS + LockSupport |
| 锁升级 | 自动升级(无锁→偏向→轻量→重量) | 无锁升级,始终是重量级锁(但通过 CAS 减少阻塞) |
| 释放机制 | 自动释放(退出同步块 / 抛异常) | 手动释放(必须在 finally 中调用 unlock()) |
| 公平性 | 仅支持非公平锁 | 支持公平 / 非公平(构造器指定) |
| 可中断性 | 不支持 | 支持(lockInterruptibly()) |
| 条件锁 | 不支持(仅 Object.wait()/notify(),单等待队列) | 支持(Condition,多等待队列) |
| 性能(JDK1.6+) | 与 ReentrantLock 接近(锁升级优化后) | 高并发下略优(灵活控制队列和 CAS) |
总结
synchronized:JVM 原生支持,无需手动管理,通过「对象头 + Monitor + 锁升级」实现,兼顾简单性和性能,适合大多数基础并发场景;ReentrantLock:基于 AQS 框架,通过「状态变量 + 同步队列 + CAS」实现,支持公平锁、可中断、条件锁等灵活特性,适合复杂并发场景(如高并发读写、精细化线程控制)。
两者的核心都是「通过互斥保证原子性,通过内存屏障保证可见性和有序性」,但实现层面的差异导致了功能和性能的区别 —— 实际开发中,简单场景用 synchronized(不易出错),复杂场景用 ReentrantLock(灵活可控)。
到此这篇关于深入理解Java中的synchronized 和 ReentrantLock 底层原理的文章就介绍到这了,更多相关java synchronized 和 reentrantlock内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
