java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > java  synchronized 和 reentrantlock

深入理解Java中的synchronized 和 ReentrantLock 底层原理

作者:北郭guo

synchronized和ReentrantLock是Java中最核心的两种锁实现,前者是JVM内置锁(隐式锁),后者是JUC显式锁(基于AQS 框架),本文给大家介绍Java中的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 内部包含三个核心结构:

👉 本质:Monitor 通过操作系统的「互斥量(Mutex)」实现线程阻塞 / 唤醒,是重量级锁性能开销的主要来源。

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

synchronized 并非一开始就是重量级锁,JVM 会根据「竞争激烈程度」自动触发「锁升级」(无锁→偏向锁→轻量级锁→重量级锁),目的是在 “无竞争” 和 “激烈竞争” 场景下都能保证性能。

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

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

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

3. 解锁与自动释放机制

synchronized 无需手动解锁,JVM 会在以下场景自动释放锁:

解锁的核心操作:

4. 内存语义的实现

synchronized 除了互斥,还能保证可见性和有序性,底层通过 内存屏障(Memory Barrier) 实现:

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

ReentrantLock 是 java.util.concurrent.locks 包下的显式锁,基于 AQS(AbstractQueuedSynchronizer,抽象队列同步器) 实现,支持公平锁 / 非公平锁、可中断、条件锁等灵活特性,完全由 Java 代码实现(无需 JVM 介入)。

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

AQS 是 JUC 同步工具的基础(如 ReentrantLockSemaphoreCountDownLatch),其核心设计是「状态变量 + 同步队列」,所有锁的逻辑都围绕 AQS 扩展。

(1)AQS 的核心结构

(2)AQS 的核心模板方法

AQS 定义了一套「模板方法」,子类(如 ReentrantLock 的内部类 Sync)只需实现以下抽象方法,即可完成锁的逻辑:

👉 本质: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;
}

加锁核心步骤总结

  1. 线程先尝试「CAS 抢锁」(插队,非公平锁的核心);
  2. 抢锁成功:设置 state=1,标记当前线程为独占线程;
  3. 抢锁失败:
    • 若当前线程已持有锁(重入):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;
}

解锁核心步骤总结

  1. 线程调用 unlock()state 减 1;
  2. 若 state == 0:完全释放锁,清空独占线程,唤醒 AQS 同步队列中的后继线程;
  3. 若 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());
}

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

(1)可中断锁(lockInterruptibly()

ReentrantLock 支持线程在等待锁时被中断,底层通过 AQS 的 acquireInterruptibly() 实现:

(2)条件锁(Condition

ReentrantLock 可通过 newCondition() 获取 Condition 对象,实现 “线程等待 / 唤醒” 的精细化控制(类似 Object.wait()/notify(),但支持多个等待队列):

三、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(不易出错),复杂场景用 ReentrantLock(灵活可控)。

到此这篇关于深入理解Java中的synchronized 和 ReentrantLock 底层原理的文章就介绍到这了,更多相关java synchronized 和 reentrantlock内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文