根爱ai创作助手:Java并发面试必问——synchronized vs ReentrantLock全解析

北京时间为2026年4月10日,正值2026春招季与年中面试高峰,Java并发编程已成为大厂技术面试中的核心高频考点-8。本文由根爱ai创作助手精心打磨,从基础概念到底层原理,从代码示例到面试要点,带你彻底吃透这对面试官最爱问的“锁兄弟”。

📌 本文你将掌握

  • synchronized 与 Lock(ReentrantLock)的本质区别

  • synchronized 锁升级机制(无锁→偏向锁→轻量级锁→重量级锁)

  • ReentrantLock 底层 AQS 实现原理

  • 生产-消费者场景代码实战

  • 高频面试题标准答案

在多线程编程中,共享资源的线程安全是一个无法回避的核心问题。synchronized 作为 Java 语言层面的内置锁,凭借“简单易用”成为无数初学者的第一选择;而 ReentrantLock 作为 JUC 包中功能更强大的显式锁,则是中高级开发者在复杂场景下的不二之选。

很多开发者对这个知识点的掌握停留在表面:知道两者都是锁,却说不出本质区别;会用 synchronized,却不理解锁升级机制;记得 ReentrantLock 比 synchronized 灵活,却说不出 AQS 的实现原理。这些恰恰是面试官最想考察的地方。

本文将由浅入深,从痛点切入到原理剖析,从代码实战到面试要点,帮你建立完整的知识链路。

一、痛点切入:为什么需要显式锁?

先看一个经典的计数器场景:

java
复制
下载
// 使用 synchronized 的实现(旧方式)
public class Counter {
    private int count = 0;
    
    public synchronized void increment() {
        count++;   // 实际上 count++ 不是原子操作:读取→加1→写回
    }
    
    public int getCount() {
        return count;
    }
}

在单线程环境下没有问题,但多线程同时调用 increment() 时,count++ 的三条指令交错执行,就会出现数据竞争(Race Condition),最终结果小于预期-67synchronized 通过互斥锁解决了数据竞争问题,但它的局限性也很明显:

  1. 无法中断:线程在等待锁时只能被阻塞,无法响应中断信号

  2. 无超时机制:可能永久等待,无法设置获取锁的超时时间

  3. 仅支持非公平锁:无法保证等待时间最长的线程先获得锁

  4. 单一等待队列:通过 wait()/notify() 只能实现一个等待队列

正是为了弥补这些不足,Java 5 引入了 java.util.concurrent.locks 包,其中 ReentrantLock 提供了更强大的锁控制能力-61

二、核心概念讲解:synchronized(内置锁)

定义

synchronized 是 Java 提供的一个关键字,JVM 层面的隐式锁(Intrinsic Lock),无需手动释放,JVM 会自动完成加锁和解锁操作。

核心特点

  • 可重入性:同一线程可以多次获取同一把锁

  • 默认非公平锁:线程竞争时随机获取锁,没有公平性保证

  • 自动加锁/解锁:方法或代码块执行完自动释放,无需手动处理

  • 底层依赖:对象头的 Mark Word + 监视器锁(ObjectMonitor)

三种使用方式

java
复制
下载
public class SynchronizedDemo {
    // 1. 实例方法锁:锁当前对象 this
    public synchronized void objectLock() {
        System.out.println(Thread.currentThread().getName() + " 获取对象锁");
    }
    
    // 2. 静态方法锁:锁 Class 对象
    public static synchronized void classLock() {
        System.out.println(Thread.currentThread().getName() + " 获取类锁");
    }
    
    // 3. 代码块锁:自定义锁对象(灵活度最高)
    private final Object lockObj = new Object();
    public void blockLock() {
        synchronized (lockObj) {
            System.out.println(Thread.currentThread().getName() + " 获取代码块锁");
        }
    }
}

-22

锁升级机制(JDK 1.6 后的关键优化)

早期版本中,synchronized 被称为“重量级锁”,每次加锁都需要操作系统介入,性能较差。JDK 1.6 引入了锁升级机制,锁状态会经历以下路径-21

text
复制
下载
无锁 → 偏向锁 → 轻量级锁 → 重量级锁
锁状态特点适用场景
偏向锁偏向第一个线程,只做标记,不加真锁单线程反复访问同一资源
轻量级锁使用 CAS(Compare-And-Swap)尝试获取锁,自旋等待多线程短时间执行,竞争不激烈
重量级锁阻塞等待,线程进入等待队列,不再消耗 CPU高竞争、锁持有时间长的场景

-67

理解偏向锁:偏向锁不是真的加锁,而是在对象头中做一个标记,记录这个锁属于哪个线程。如果后续没有其他线程竞争,就可以避免加锁解锁的开销。只有当竞争出现时,才升级到轻量级锁-21

锁升级是不可逆的:一旦升级到重量级锁,无法回退到轻量级锁,需要新的锁对象才能重新开始-21

三、关联概念讲解:ReentrantLock(显式锁)

定义

ReentrantLockjava.util.concurrent.locks.Lock 接口的实现类,位于 JUC 包中,由并发编程大师 Doug Lea 编写,是一种需要手动加锁和释放锁的显式锁-22

核心特点

  • 可重入性:支持同一线程重复获取锁

  • 可选择公平/非公平:通过构造函数指定

  • 支持中断lockInterruptibly() 支持响应中断

  • 支持超时tryLock(timeout, unit) 设置等待时限

  • 支持多条件队列:通过 Condition 实现精细的线程协作

基本使用方式

java
复制
下载
public class ReentrantLockDemo {
    // 公平锁(按请求顺序获取),也可传 false 或使用无参构造(默认非公平)
    private static final ReentrantLock lock = new ReentrantLock(true);
    
    public static void doTask() {
        lock.lock();   // 手动加锁
        try {
            // 临界区代码
            System.out.println(Thread.currentThread().getName() + " 获取锁");
        } catch (Exception e) {
            Thread.currentThread().interrupt();
        } finally {
            lock.unlock();   // 必须手动释放,否则死锁!
        }
    }
}

-22

公平锁 vs 非公平锁

ReentrantLock 通过构造函数区分公平锁和非公平锁-37

java
复制
下载
// 非公平锁(默认,性能更高)
ReentrantLock lock = new ReentrantLock();

// 公平锁
ReentrantLock fairLock = new ReentrantLock(true);

公平锁:按等待时间顺序获取锁,避免线程饥饿,但性能较低,因为需要维护队列顺序-37

非公平锁:允许新来的线程直接竞争锁,能插队成功就立即占有,失败再进入队列排队。这种“插队”策略减少了上下文切换,性能更高-37

为什么默认是非公平锁? 公平锁虽然公平,但每个线程都要加入队列排队、阻塞、再由阻塞变为运行,上下文切换开销很大。非公平锁允许插队,减少了线程切换,在大多数场景下性能更优-

四、概念关系与区别总结

synchronized 是 JVM 层面的语言级锁,追求简洁易用;ReentrantLock 是基于 AQS 框架的API 级锁,追求灵活可扩展。

一句话记忆synchronized 是 JVM 帮你自动管理的“傻瓜锁”,ReentrantLock 是你自己掌管的“瑞士军刀锁”。

详细对比表

维度synchronizedReentrantLock
实现层级JVM 内置关键字,通过监视器实现JDK API 类,基于 AQS 实现
使用方式隐式获取/释放,自动完成显式调用 lock()/unlock()
可重入性✅ 支持✅ 支持
锁类型仅支持非公平锁支持公平锁和非公平锁
条件变量wait()/notify(),单一队列可创建多个 Condition 对象
中断响应❌ 不支持lockInterruptibly() 支持
超时机制❌ 不支持tryLock(timeout, unit) 支持
性能对比JDK 1.6 优化后性能接近高竞争场景表现更稳定

-11

五、代码示例:生产者-消费者模型

一个实际的生产者-消费者场景最能体现两者的差异。以下是使用 ReentrantLock + Condition 的实现:

java
复制
下载
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class ProducerConsumerDemo {
    // 可消费的产品数量
    private static int productNum = 0;
    private static final ReentrantLock lock = new ReentrantLock();
    
    // 为生产者、消费者分别创建不同的等待队列
    private static final Condition consumer = lock.newCondition();
    private static final Condition producer = lock.newCondition();
    
    public static void main(String[] args) {
        // 生产者线程
        new Thread(() -> {
            while (true) {
                lock.lock();
                try {
                    // 有产品可消费时,生产者等待
                    while (productNum > 0) {
                        producer.await();
                    }
                    // 生产 1-4 件产品
                    int total = (int) (Math.random()  4) + 1;
                    for (int i = 0; i < total; i++) {
                        System.out.println("生产者生产了产品,当前数量:" + (++productNum));
                    }
                    // 唤醒消费者
                    consumer.signalAll();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        }, "生产者").start();
        
        // 消费者线程
        new Thread(() -> {
            while (true) {
                lock.lock();
                try {
                    // 无产品可消费时,消费者等待
                    while (productNum == 0) {
                        consumer.await();
                    }
                    // 消费所有产品
                    int total = productNum;
                    while (productNum > 0) {
                        System.out.println("消费者消费了产品,剩余数量:" + (--productNum));
                    }
                    // 唤醒生产者
                    producer.signalAll();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        }, "消费者").start();
    }
}

-47

如果使用 synchronized 实现上述逻辑,只能通过一个 wait/notify 队列实现,无法区分生产者和消费者的等待原因,容易造成“生产者唤醒生产者”或“消费者唤醒消费者”的低效情况。

六、底层原理:技术支撑

synchronized 的底层支撑

  1. 对象头(Mark Word) :锁信息记录在 Java 对象头的 Mark Word 中

  2. 监视器锁(ObjectMonitor) :重量级锁阶段,每个对象关联一个 Monitor 对象

  3. 字节码指令:编译后在同步代码块前后插入 monitorentermonitorexit 指令

  4. CAS 操作:轻量级锁阶段通过 CAS 实现自旋获取锁

ReentrantLock 的底层支撑:AQS

AQS(AbstractQueuedSynchronizer) 是 JUC 包中构建锁和同步器的核心框架,ReentrantLockCountDownLatchSemaphore 都构建在它之上-35

AQS 的核心架构包含三大组件-35

  • volatile int state:同步状态,state=0 表示无锁,state>0 表示有锁(值表示重入次数)

  • CLH 虚拟双向队列:FIFO 等待队列,用于存放获取锁失败的线程

  • Node 节点:封装等待线程及其状态(SIGNAL、CANCELLED 等)

当线程获取锁失败时,会被封装成 Node 节点加入队列尾部并阻塞;持有锁的线程释放锁时,会唤醒队列中的后继节点-35-

七、高频面试题与参考答案

Q1:synchronized 和 ReentrantLock 有什么区别?

参考答案(分点作答,逻辑清晰):

  1. 实现层级不同:synchronized 是 JVM 关键字,底层由 monitorenter/monitorexit 指令实现;ReentrantLock 是 JDK API 类,基于 AQS 框架实现。

  2. 使用方式不同:synchronized 隐式加锁/释放,无需手动操作;ReentrantLock 需显式调用 lock()/unlock(),且 unlock() 必须放在 finally 中。

  3. 锁类型不同:synchronized 仅支持非公平锁;ReentrantLock 支持公平锁和非公平锁。

  4. 中断能力不同:synchronized 等待锁时不可中断;ReentrantLock 支持 lockInterruptibly()

  5. 超时机制不同:synchronized 无超时机制;ReentrantLock 支持 tryLock(timeout, unit)

  6. 条件队列不同:synchronized 只有一个等待队列(wait/notify);ReentrantLock 可创建多个 Condition 对象,实现精细化的线程协作。

Q2:synchronized 的锁升级机制是什么?

参考答案

JDK 1.6 对 synchronized 进行了优化,引入锁升级机制,锁状态变化路径为:无锁 → 偏向锁 → 轻量级锁 → 重量级锁,且不可逆。

  • 偏向锁:偏向第一个线程,只做标记不加真锁,避免加锁开销。无竞争时保持此状态。

  • 轻量级锁:出现锁竞争时升级,通过 CAS 自旋尝试获取锁,不阻塞线程,避免操作系统调度开销。

  • 重量级锁:竞争激烈或自旋次数超限时升级,线程阻塞等待,通过 ObjectMonitor 实现。

锁升级使 synchronized 在低竞争场景性能接近 ReentrantLock。

Q3:ReentrantLock 的公平锁和非公平锁有什么区别?为什么默认是非公平锁?

参考答案

  • 公平锁:按线程等待时间顺序获取锁(FIFO),避免线程饥饿,但需要维护队列,上下文切换频繁,性能较低。

  • 非公平锁:新来的线程先尝试插队竞争锁,成功则直接获取,失败再进入队列排队。性能更高,但可能导致线程饥饿。

为什么默认非公平锁:公平锁需要每次都要阻塞和唤醒线程,上下文切换开销较大;非公平锁允许插队,减少了线程切换,在大多数场景下吞吐量更高。

Q4:什么是 AQS?ReentrantLock 如何利用 AQS 实现?

参考答案

AQS(AbstractQueuedSynchronizer)是 JUC 包中构建锁和同步器的核心框架。核心由三部分组成:volatile int state(同步状态,0 表示无锁,>0 表示有锁及重入次数)、CLH 虚拟双向 FIFO 队列(存放等待锁的线程)、Node 节点(封装线程及其等待状态)。

ReentrantLock 通过内部 Sync 类继承 AQS,并实现 tryAcquire()/tryRelease() 方法。公平锁与非公平锁的区别体现在 tryAcquire() 的实现上——公平锁先检查队列中是否有前驱节点,有则排队;非公平锁先尝试 CAS 插队。

Q5:如何选择使用 synchronized 还是 ReentrantLock?

参考答案

  • 简单同步需求(如方法/代码块互斥),优先选 synchronized,代码简洁且不易出错。

  • 需要公平锁、可中断、超时获取、多条件队列等高级特性时,选 ReentrantLock

  • 高竞争、复杂同步场景选 ReentrantLock,低竞争或简单场景选 synchronized。

性能参考:JDK 1.6 优化后,低竞争场景 synchronized 略优,高竞争场景 ReentrantLock 更稳定-61

八、总结与进阶预告

核心知识点回顾

知识点要点
synchronized 本质JVM 内置关键字,隐式锁,支持锁升级
ReentrantLock 本质JUC API 显式锁,基于 AQS 框架
核心区别自动/手动、公平性、中断、超时、条件队列
底层原理synchronized → 对象头 + ObjectMonitor;ReentrantLock → AQS + CAS + CLH 队列

易错点提醒

  • ReentrantLock 必须在 finally 中调用 unlock(),否则会造成死锁

  • 公平锁并非性能最优选择,需要根据实际场景权衡

  • synchronized 的锁升级不可逆,高竞争下会升级为重量级锁

  • lockInterruptibly() 必须在 try-catch 中捕获 InterruptedException

进阶预告

下一篇将深入剖析 AQS 源码实现,从 acquire()release(),从独占模式到共享模式,彻底搞懂 JUC 并发工具的核心底层原理,敬请期待!