目录

一、synchronized 的实现:JVM 层面的 “自动锁”

1. 核心依赖组件

(1)对象头(Mark Word):锁状态的 “存储载体”

(2)监视器(Monitor):重量级锁的 “核心同步工具”

2. synchronized 的核心实现流程(锁升级)

(1)无锁 → 偏向锁(单线程竞争)

(2)偏向锁 → 轻量级锁(低并发交替竞争)

(3)轻量级锁 → 重量级锁(高并发激烈竞争)

3. 解锁与自动释放机制

4. 内存语义的实现

二、ReentrantLock 的实现:JUC 层面的 “手动锁”

1. 核心依赖组件:AQS(锁的 “骨架”)

(1)AQS 的核心结构

(2)AQS 的核心模板方法

2. ReentrantLock 的内部结构

3. ReentrantLock 的核心实现流程(以非公平锁为例)

(1)加锁流程(lock() 方法)

(2)解锁流程(unlock() 方法)

4. 公平锁与非公平锁的实现差异

5. 可中断与条件锁的实现

(1)可中断锁(lockInterruptibly())

(2)条件锁(Condition)

三、synchronized 与 ReentrantLock 实现对比

总结


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)无锁 → 偏向锁(单线程竞争)
  • 触发条件:只有一个线程尝试获取锁。
  • 实现流程
    1. 线程 T1 第一次进入同步块时,JVM 通过 CAS 操作 修改对象的 Mark Word:将「线程 ID」写入 Mark Word,锁标志位保持 01(偏向锁标识);
    2. 后续 T1 再次进入同步块时,只需判断 Mark Word 中的线程 ID 是否为自己:
      • 是:直接进入(无需任何同步操作,零开销);
      • 否:偏向锁失效,触发升级。
  • 核心目的:优化 “单线程重复获取锁” 的场景(如单线程操作集合),避免无意义的同步开销。
(2)偏向锁 → 轻量级锁(低并发交替竞争)
  • 触发条件:有第二个线程 T2 尝试获取同一把锁(T1 未释放)。
  • 实现流程
    1. JVM 先暂停 T1,撤销偏向锁(将 Mark Word 恢复为无锁状态);
    2. T1 和 T2 各自在自己的「栈帧」中创建一个「锁记录(Lock Record)」,锁记录中存储对象 Mark Word 的副本(Displaced Mark Word);
    3. 两个线程通过 CAS 操作 竞争修改对象的 Mark Word:将 Mark Word 指向自己的锁记录;
      • 成功:获取轻量级锁,进入同步块;
      • 失败:说明竞争存在,进入自旋重试(循环尝试 CAS,避免阻塞)。
  • 核心目的:优化 “多线程交替执行” 的场景(无激烈竞争),通过自旋减少线程阻塞 / 唤醒的开销(用户态操作,比内核态高效)。
(3)轻量级锁 → 重量级锁(高并发激烈竞争)
  • 触发条件:自旋次数超过阈值(JDK1.6 后为「自适应自旋」,根据前一次自旋成功率动态调整),或有更多线程参与竞争。
  • 实现流程
    1. 自旋失败的线程放弃竞争,将锁升级为重量级锁(修改 Mark Word 指向 Monitor);
    2. 线程进入 Monitor 的 EntryList 队列,放弃 CPU 资源(进入 阻塞状态,由操作系统调度);
    3. 持有锁的线程释放锁时,通过操作系统唤醒 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 同步工具的基础(如 ReentrantLockSemaphoreCountDownLatch),其核心设计是「状态变量 + 同步队列」,所有锁的逻辑都围绕 AQS 扩展。

(1)AQS 的核心结构
  • 状态变量(state)volatile int state,存储锁的状态:
    • state = 0:锁未被持有;
    • state > 0:锁已被持有,值等于「重入次数」(支持可重入);
  • 同步队列(CLH 队列):双向链表,每个节点是 Node 对象,存储等待锁的线程、等待状态(如 CANCELLEDWAITING)等;
  • 独占线程(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 的两个子类实现「公平锁」和「非公平锁」:

java

运行

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() 方法)

java

运行

// 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;
}

加锁核心步骤总结

  1. 线程先尝试「CAS 抢锁」(插队,非公平锁的核心);
  2. 抢锁成功:设置 state=1,标记当前线程为独占线程;
  3. 抢锁失败:
    • 若当前线程已持有锁(重入):state += 1,直接成功;
    • 若锁被其他线程持有:将线程封装为 Node 加入 AQS 同步队列,调用 LockSupport.park() 阻塞线程。
(2)解锁流程(unlock() 方法)

java

运行

// 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;
}

解锁核心步骤总结

  1. 线程调用 unlock()state 减 1;
  2. 若 state == 0:完全释放锁,清空独占线程,唤醒 AQS 同步队列中的后继线程;
  3. 若 state > 0:仅更新 state(重入锁未完全释放)。
4. 公平锁与非公平锁的实现差异

核心差异在 tryAcquire() 方法中,公平锁会多一步「判断队列是否有等待线程」:

java

运行

// 公平锁 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 实现对比

维度synchronizedReentrantLock
实现层面JVM 内置(C++ 实现,如 HotSpot 的 synchronizer.cppJava 代码层面(基于 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(灵活可控)。