AI小小助手:2026.04.08 彻底讲透Java ReentrantLock可重入锁
📍 一、基础信息配置
文章标题:AI小小助手:2026.04.08 彻底讲透Java ReentrantLock可重入锁
目标读者:技术入门/进阶学习者、在校学生、面试备考者、Java后端开发工程师
文章定位:技术科普 + 原理讲解 + 代码示例 + 面试要点,兼顾易懂性与实用性
写作风格:条理清晰、由浅入深、语言通俗、重点突出,少晦涩理论,多对比与示例
核心目标:让读者理解概念、理清逻辑、看懂示例、记住考点,建立完整知识链路
📍 开篇引入
在Java并发编程的面试中,ReentrantLock几乎是一个必问的知识点。但很多学习者的现状是:会用synchronized写同步代码块,却答不出ReentrantLock和synchronized的核心区别;知道ReentrantLock能实现公平锁,却说不清公平锁底层是怎么运作的;见过lock和unlock的写法,却不理解可重入到底是什么意思。本文由AI小小助手整理了最新资料,将从痛点切入,由浅入深地讲解ReentrantLock的概念、原理、代码示例和面试考点,帮助你建立完整的知识链路。
📍 痛点切入:为什么需要ReentrantLock
在synchronized出现之前和早期,Java的线程同步主要依赖synchronized关键字。但synchronized存在一个明显的痛点:控制能力太弱。来看一个典型场景:
public class Counter { private int count = 0; public synchronized void increment() { count++; } public int getCount() { return count; }}这种写法简单直接,但存在几个问题:
无法尝试获取锁:如果锁被占用,线程只能阻塞等待,没有“试一下拿不到就算了”的选项;
无法超时获取:没有“最多等1秒”这样的控制,可能导致线程无限等待;
无法中断等待:等待锁的过程中无法响应中断信号;
只有一个条件队列:wait/notify的唤醒粒度粗,容易产生“惊群效应”。
这些问题在高并发、对响应性有要求的场景下尤为突出。于是在Java 5中,Doug Lea设计了ReentrantLock——一个比synchronized更灵活、更可控的显式锁-10。
📍 核心概念讲解:ReentrantLock的定义与特性
ReentrantLock,英文全称即ReentrantLock,中文常译为“可重入锁”,是java.util.concurrent.locks包下Lock接口的一个实现类。
官方定义(Java Platform SE 8):“A reentrant mutual exclusion Lock with the same basic behavior and semantics as the implicit monitor lock accessed using synchronized methods and statements, but with extended capabilities.”-2
翻译过来:它是一个可重入的互斥锁,与synchronized具有相同的基础行为和语义,但扩展了更多功能。
🔑 拆解关键词
互斥锁(Mutual Exclusion Lock) :同一时刻,只能有一个线程持有该锁;
可重入(Reentrant) :同一个线程可以多次获取同一把锁,而不会把自己卡死;
显式锁(Explicit Lock) :需要手动调用
lock()加锁、unlock()解锁,不像synchronized那样由JVM自动管理。
🏠 生活化类比
可以把ReentrantLock想象成一间带门禁卡的工作室:
你第一次刷卡进去,门锁上了,别人进不来——这是互斥;
你在里面做了一半事情,突然要去隔壁拿个东西,于是刷卡出门;回来再刷卡进门——但此时门不会把你锁在外面,因为你已经持有“工作证”了——这就是可重入,同一个人的同一次访问被重复允许。
💡 核心价值
ReentrantLock的价值在于:把synchronized的“自动挡”变成了“手动挡” ——更灵活,但需要你对锁的管理更谨慎。
📍 关联概念讲解:AQS(AbstractQueuedSynchronizer)
ReentrantLock的底层实现,离不开一个重量级框架——AQS。
AQS定义:AbstractQueuedSynchronizer(抽象队列同步器),是JUC锁框架中最核心的类,用于构建锁和其他同步器组件-19。
它与ReentrantLock的关系:AQS是“引擎”,ReentrantLock是“整车” 。ReentrantLock把具体的加锁逻辑(比如公平还是非公平)交给AQS去实现,自己只需要调用AQS提供的方法即可。
🏠 生活化类比
如果把锁比作高铁购票系统:
AQS就是铁轨和调度系统——它负责管理排队顺序(FIFO队列)、控制票务状态(state变量);
ReentrantLock就是具体的购票终端——你刷身份证买票,终端调用后台系统帮你排队、分配座位。
🔧 AQS的核心机制
AQS内部维护了两样核心数据-53:
volatile int state:同步状态变量。对于ReentrantLock,
state = 0表示未锁定,state > 0表示锁定状态,且state的值代表了当前线程的重入次数;FIFO等待队列(CLH队列变体) :一个双向链表,存储所有等待锁的线程。
当一个线程获取锁失败时,AQS会把它封装成一个Node节点,放入队列尾部并挂起;当锁被释放时,AQS会从队列头部唤醒下一个线程-。
⚖️ ReentrantLock vs synchronized 核心对比
| 维度 | synchronized | ReentrantLock |
|---|---|---|
| 实现层级 | JVM内置(字节码指令) | JDK API(Java代码) |
| 锁获取 | 自动获取,进入代码块即获取 | 手动调用lock() |
| 锁释放 | 自动释放(异常也会释放) | 必须手动unlock(),通常在finally中 |
| 可重入性 | ✅ 支持 | ✅ 支持 |
| 尝试获取锁 | ❌ 不支持 | ✅ tryLock() |
| 超时获取 | ❌ 不支持 | ✅ tryLock(timeout, unit) |
| 可中断获取 | ❌ 不支持 | ✅ lockInterruptibly() |
| 公平性 | 非公平(不可指定) | 可指定公平/非公平 |
| 条件队列 | 1个(wait/notify) | 多个(Condition) |
| 性能 | JDK 1.6后大幅优化 | 高竞争下更稳定 |
-57-4
📍 概念关系与区别总结
一句话总结三者的逻辑关系:
synchronized是“自动挡”的简单锁,ReentrantLock是“手动挡”的高级锁,AQS是ReentrantLock的底层引擎。
更精确地说:
synchronized:JVM内置锁机制,自动管理,简单够用;
ReentrantLock:JDK提供的显式锁,功能丰富,灵活可控;
AQS:ReentrantLock的底层实现框架,提供FIFO队列和状态管理。
📍 代码示例演示
基础使用(最标准写法)
import java.util.concurrent.locks.ReentrantLock;public class Counter { private final ReentrantLock lock = new ReentrantLock(); // 默认非公平锁 private int count = 0; public void increment() { lock.lock(); // 加锁 try { count++; // 临界区代码 } finally { lock.unlock(); // 释放锁(必须在finally中) } } public int getCount() { return count; }}⚠️ 关键注意:unlock()必须放在finally块中,确保即使临界区抛出异常,锁也能被正确释放,否则会导致死锁-2。
高级用法示例
import java.util.concurrent.TimeUnit;import java.util.concurrent.locks.ReentrantLock;public class AdvancedLockDemo { private final ReentrantLock lock = new ReentrantLock(true); // 公平锁 // 尝试获取锁(不阻塞) public void tryLockDemo() { if (lock.tryLock()) { try { // 获取成功,执行业务 System.out.println("获取锁成功"); } finally { lock.unlock(); } } else { // 获取失败,执行备选方案 System.out.println("锁被占用,执行备选逻辑"); } } // 超时获取锁 public void tryLockWithTimeout() throws InterruptedException { if (lock.tryLock(2, TimeUnit.SECONDS)) { try { System.out.println("2秒内成功获取锁"); } finally { lock.unlock(); } } else { System.out.println("超时未获取到锁"); } } // 可中断获取锁 public void interruptibleLock() throws InterruptedException { lock.lockInterruptibly(); // 等待过程中可响应中断 try { System.out.println("获取锁成功"); } finally { lock.unlock(); } }}📍 底层原理:ReentrantLock是怎么工作的
ReentrantLock的底层实现可以用一个简单的流程概括:
🔄 加锁流程(以非公平锁为例)
线程调用
lock(),尝试通过CAS(Compare And Swap)操作将AQS的state从0改为1;如果CAS成功,说明当前没有其他线程持有锁,当前线程获取锁成功,并将自己记录为锁的持有线程;
如果CAS失败(
state != 0),检查持有锁的线程是不是当前线程本身:如果是(可重入场景),则
state加1,记录重入次数;如果不是,当前线程被封装成
Node节点,进入AQS的FIFO等待队列,并被挂起(park);当锁被释放时,
state减1,当state变为0时,从队列头部唤醒下一个等待线程-27。
⚖️ 公平锁 vs 非公平锁
非公平锁(默认) :新来的线程会直接尝试CAS抢锁,不管队列里有没有人在等。优点是吞吐量更高,缺点是可能造成“线程饥饿”,即某些线程迟迟拿不到锁-53;
公平锁:新来的线程先检查等待队列中是否有其他线程在排队,如果有就乖乖排队。优点是公平性好,缺点是吞吐量较低(因为需要更多的上下文切换)-。
Oracle官方文档明确指出:使用公平锁的程序,吞吐量通常比默认的非公平锁要低,甚至可能慢很多,但能保证没有线程饥饿问题-2。
🔧 技术支撑点
ReentrantLock底层依赖三个核心技术:
CAS(Compare And Swap) :无锁的原子操作,用于快速修改
state状态;AQS:提供FIFO队列和线程阻塞/唤醒机制(通过
LockSupport.park/unpark);volatile:保证
state变量的内存可见性。
📍 高频面试题与参考答案
面试题1:ReentrantLock和synchronized有什么区别?
参考答案(建议背诵):
实现层面:synchronized是JVM内置关键字,基于监视器模式(monitor),由C++实现;ReentrantLock是JDK API层面的锁,基于AQS,由Java实现;
锁管理:synchronized自动加锁和解锁,异常时JVM自动释放;ReentrantLock需手动
lock()和unlock(),必须在finally中释放;灵活性:ReentrantLock支持尝试获取(
tryLock())、超时获取(tryLock(timeout))、可中断获取(lockInterruptibly()),synchronized均不支持;公平性:synchronized默认非公平且不可改变;ReentrantLock可通过构造参数指定公平/非公平;
条件队列:synchronized只有一个等待队列(
wait/notify);ReentrantLock可创建多个Condition,实现精准唤醒;性能:JDK 1.6对synchronized进行了锁升级优化(偏向锁→轻量级锁→重量级锁),两者性能差距已缩小;但在高竞争场景下,ReentrantLock表现更稳定-10-。
面试题2:什么是可重入锁?ReentrantLock是如何实现可重入的?
参考答案:
可重入锁指的是同一个线程可以多次获取同一把锁,而不会造成死锁。ReentrantLock内部维护了一个state计数器和exclusiveOwnerThread(当前持有锁的线程)。当线程调用lock()时,会检查:如果state == 0,说明锁未被占用,通过CAS将state设为1并记录当前线程;如果state > 0且持有锁的线程就是当前线程,则state++;每次unlock()时,state--;只有当state归零时,锁才真正释放-46-27。
面试题3:公平锁和非公平锁的区别?如何选择?
参考答案:
非公平锁(默认) :线程在请求锁时,会先尝试一次CAS抢锁,抢不到才排队。优点是吞吐量高、上下文切换少;缺点是可能造成线程饥饿;
公平锁:线程严格按照申请锁的绝对时间顺序排队获取,不会插队。优点是公平性好、无饥饿;缺点是吞吐量较低,可能慢很多。
选择建议:一般情况下使用默认的非公平锁即可,因为吞吐量更高;只有在业务对公平性有特殊要求(如避免某些线程长时间得不到锁)时,才考虑公平锁--2。
面试题4:ReentrantLock的底层原理是什么?
参考答案:
ReentrantLock底层基于AQS(AbstractQueuedSynchronizer) 实现。AQS内部维护了一个volatile int state(表示同步状态)和一个FIFO的CLH队列变体(存储等待线程)。加锁过程:线程通过CAS尝试修改state,成功则获取锁;失败则进入等待队列并被park挂起。释放锁过程:state减1,减到0时唤醒队列中的下一个线程。ReentrantLock通过两个内部类(FairSync和NonfairSync)实现公平/非公平两种策略-27。
面试题5:ReentrantLock使用中需要注意哪些问题?
参考答案:
必须在finally中释放锁,否则异常可能导致锁永远不释放,造成死锁;
加锁和释放锁的次数必须匹配,因为ReentrantLock支持可重入,每调用一次
lock()就要对应一次unlock();使用
tryLock()时,要检查返回值,根据是否获取锁成功来决定是否执行后续操作和释放锁;公平锁虽好,但吞吐量较低,非必要不推荐使用-4。
📍 结尾总结
回顾全文的核心知识点:
| 知识模块 | 核心要点 |
|---|---|
| 核心概念 | ReentrantLock = 可重入互斥锁 + 显式锁 |
| 与synchronized对比 | 更灵活:tryLock、超时、可中断、多Condition、可选公平性 |
| 底层原理 | 基于AQS,依赖CAS + state变量 + FIFO队列 |
| 公平/非公平 | 默认非公平(高吞吐),公平锁(无饥饿但慢) |
| 使用规范 | lock()/unlock()成对出现,unlock()必须在finally中 |
重点和易错点:
最容易翻车的地方:忘记在finally中调用unlock() ;
最容易混淆的地方:公平锁≠线程调度公平,它只保证锁的获取顺序,不保证线程被CPU调度的顺序;
最容易忽略的地方:tryLock()要检查返回值,不能直接认为一定获取到了锁。
下一篇预告:我们将深入AQS源码,剖析独占锁和共享锁的底层实现,帮你彻底理解CountDownLatch、Semaphore等同步器的工作原理,敬请期待!