news 2026/4/15 7:38:22

【Java线程安全实战】④ 可重入锁ReentrantLock深度拆解:如何实现线程安全的同步?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【Java线程安全实战】④ 可重入锁ReentrantLock深度拆解:如何实现线程安全的同步?

📖目录

  • 前言
  • 1. 为什么需要 ReentrantLock?它解决了什么痛点?
  • 2. 可重入:同一个线程的“免检通行证”
  • 3. 源码关键:AQS 与 CLH 队列
  • 4. 公平锁 vs 非公平锁
  • 5. 除了 ReentrantLock,还有哪些可重入锁?
  • 6. 谁在用 ReentrantLock?—— JDK 内部的应用
  • 7. 从 JDK 2 到 JDK 21:ReentrantLock 的演进
  • 8. 下一篇预告
  • 9. 经典书籍推荐
  • 10. 前文参考:

前言

在并发编程的世界里,synchronized是我们的老朋友,它像一把坚固但略显笨重的门锁,简单可靠。然而,随着应用复杂度的提升,这把“内置锁”开始显得力不从心。我们需要一把更智能、更灵活、功能更强大的“瑞士军刀”——这就是java.util.concurrent.locks.ReentrantLock(可重入锁)被设计出来的根本目的。


1. 为什么需要 ReentrantLock?它解决了什么痛点?

想象一下你在一家大型超市购物。收银台就是共享资源(比如一个银行账户),顾客就是线程。

  • synchronized的局限
    • 无法中断:如果你排在长队里(线程阻塞),突然想起家里煤气没关,想放弃结账(中断线程),对不起,做不到!你只能乖乖等到前面所有人结完账。
    • 无法超时:如果前面的人在和收银员激烈争论(死锁),你可能要等上几个小时。synchronized没有“等5分钟就走人”的选项。
    • 单一条件synchronized只有一个“等待区”。但在复杂的业务逻辑中,我们可能需要多个等待条件。比如,在生产者-消费者模型中,生产者要等“缓冲区不满”,消费者要等“缓冲区不空”。用synchronized实现起来非常别扭。

ReentrantLock 的诞生,就是为了提供synchronized所不具备的高级功能

  1. 响应中断(lockInterruptibly):排队时可以随时“拔腿就走”。
  2. 超时获取(tryLock(timeout)):设定一个最大等待时间,超时自动放弃。
  3. 公平/非公平选择:可以选择“先到先得”(公平锁),也可以允许“插队”以提高吞吐量(非公平锁,默认)。
  4. 多条件变量(newCondition()):可以创建多个“VIP等待室”,每个等待室对应不同的唤醒条件。

这些特性让ReentrantLock成为构建高性能、高可靠并发应用的基石。


2. 可重入:同一个线程的“免检通行证”

“可重入”是ReentrantLock的核心特性之一。大白话解释:如果一个线程已经持有了这把锁,那么它再次请求这把锁时,不会被自己挡住,而是可以直接进入

这就像你在家,拿着自家大门的钥匙。你从客厅去厨房(第一次获取锁),再从厨房去卧室(第二次获取锁),不需要每次都把钥匙拔出来再插进去。系统知道“哦,是你啊,随便进”。

技术实现ReentrantLock内部通过两个关键变量实现可重入:

  • private volatile int state:记录锁被持有的次数。0表示未被持有,>0表示已被持有,数值即为重入次数。
  • private transient Thread exclusiveOwnerThread:记录当前持有锁的线程。

当一个线程尝试获取锁时,如果发现state > 0exclusiveOwnerThread就是自己,那么它只需将state加1即可,无需排队。释放锁时,则将state减1,直到减为0,才真正释放锁并唤醒其他线程。


3. 源码关键:AQS 与 CLH 队列

ReentrantLock的强大并非凭空而来,其背后是 Java 并发包的灵魂——AbstractQueuedSynchronizer(AQS)

你可以把 AQS 想象成一个极其高效的“排队管理系统”。当多个线程争抢一把ReentrantLock失败时,AQS 会将它们组织成一个CLH (Craig, Landin, and Hagersten) 队列。这是一个虚拟的双向链表,每个节点代表一个等待中的线程。

核心流程

  1. 抢占:线程首先尝试通过 CAS (Compare-And-Swap) 原子操作直接修改state为1,抢到锁就走。
  2. 入队:如果抢不到,就把自己包装成一个Node节点,追加到 CLH 队列的尾部。
  3. 挂起:入队后,线程会检查前驱节点的状态。如果前驱是有效的等待节点,当前线程就会调用LockSupport.park()进入“睡眠”状态,不再消耗 CPU。
  4. 唤醒:当持有锁的线程执行unlock()时,它会将state减1。如果state变为0,说明锁已完全释放,此时它会唤醒 CLH 队列中的头节点的下一个节点(即队首等待者)。
  5. 重试:被唤醒的线程从park()中返回,再次尝试获取锁(此时有很大概率成功),成功后将自己设为新的头节点。

类图关系

has-a

«interface»

Lock

+lock()

+unlock()

+tryLock()

+newCondition()

ReentrantLock

-Sync sync

+lock()

+unlock()

«abstract»

AbstractQueuedSynchronizer

-volatile int state

-Node head

-Node tail

+acquire(int)

+release(int)

«abstract»

Sync

-NonfairSync nonfairSync

-FairSync fairSync

NonfairSync

+lock()

FairSync

+lock()


4. 公平锁 vs 非公平锁

这是ReentrantLock提供的一个重要选择。

  • 非公平锁 (NonfairSync, 默认)

    • 行为:新来的线程会无视CLH队列,直接尝试抢占锁。
    • 优点:吞吐量更高,因为减少了线程上下文切换的开销。
    • 缺点:可能导致队列中的线程“饿死”(长时间得不到锁)。
    • 生活比喻:就像食堂打饭,新来的人看到窗口空了,不管后面有没有排队的人,直接冲上去打饭。
  • 公平锁 (FairSync)

    • 行为:新来的线程会先检查 CLH 队列。如果队列不为空,它会乖乖排到队尾,保证“先来后到”。
    • 优点:保证了线程调度的公平性,避免饿死。
    • 缺点:吞吐量较低,因为每次都要检查队列。
    • 生活比喻:严格的排队制度,后来者必须站到最后。

如何选择?除非你的业务对公平性有硬性要求(如金融交易系统),否则强烈建议使用默认的非公平锁,以获得最佳性能。


5. 除了 ReentrantLock,还有哪些可重入锁?

在 Java 的世界里,可重入是一个非常普遍且重要的特性。

  1. synchronized关键字:这是最经典的可重入锁。它的实现基于 JVM 的 Monitor 对象,原理与ReentrantLock类似,只是由 JVM 底层管理。
  2. ReentrantReadWriteLock:读写锁,其内部的ReadLockWriteLock都是可重入的。允许多个读线程同时访问,但写线程独占。
  3. StampedLock(JDK 8+):一种更高级的读写锁,提供了乐观读模式,性能在某些场景下优于ReentrantReadWriteLock。它的写锁也是可重入的。

6. 谁在用 ReentrantLock?—— JDK 内部的应用

ReentrantLockjava.util.concurrent(JUC) 包的基石,许多我们常用的线程安全数据结构都依赖于它:

数据结构使用的锁类型
ArrayBlockingQueueReentrantLock
LinkedBlockingQueueReentrantLock(takeLock & putLock)
DelayQueueReentrantLock
PriorityBlockingQueueReentrantLock
ConcurrentHashMap(JDK 7)ReentrantLock(分段锁)
ThreadPoolExecutorReentrantLock(用于管理 worker 线程)

可以看到,几乎所有需要精细控制同步逻辑的 JUC 组件,背后都有ReentrantLock的身影。


7. 从 JDK 2 到 JDK 21:ReentrantLock 的演进

  • JDK 1.5 (JSR-166)ReentrantLock首次被引入 Doug Lea 的java.util.concurrent包,彻底改变了 Java 并发编程的格局。
  • JDK 6:对锁的性能进行了大量优化,包括偏向锁、轻量级锁等,但这些主要针对synchronizedReentrantLock的 AQS 框架也日趋成熟。
  • JDK 9+ReentrantLock的实现细节持续优化,但 API 保持稳定。社区的关注点逐渐转向更高级的并发工具,如CompletableFutureVarHandle等。
  • JDK 21 (Loom 项目预览):虽然 Loom 引入了虚拟线程(Virtual Threads),极大地简化了并发编程,但ReentrantLock在平台线程(Platform Threads)和需要精确控制的场景中,依然是不可替代的。值得注意的是,虚拟线程与synchronized锁兼容,但与ReentrantLock不兼容,因为后者会阻塞底层平台线程。

示例代码:基本用法与中断响应

下面是一个完整的例子,展示了ReentrantLock的基本用法、可重入性以及响应中断的能力。

// 【插入】完整类代码:ReentrantLockDemo.javaimportjava.util.concurrent.locks.ReentrantLock;importjava.util.concurrent.TimeUnit;publicclassReentrantLockDemo{privatestaticfinalReentrantLocklock=newReentrantLock();// 展示可重入性publicstaticvoidreentrantMethod(){lock.lock();try{System.out.println(Thread.currentThread().getName()+" entered reentrantMethod");// 同一线程再次获取锁innerMethod();}finally{lock.unlock();}}privatestaticvoidinnerMethod(){lock.lock();try{System.out.println(Thread.currentThread().getName()+" entered innerMethod");}finally{lock.unlock();}}// 展示中断响应publicstaticvoidinterruptibleMethod()throwsInterruptedException{// 使用 lockInterruptibly 可以响应中断lock.lockInterruptibly();try{System.out.println(Thread.currentThread().getName()+" acquired lock in interruptibleMethod");// 模拟长时间持有锁TimeUnit.SECONDS.sleep(10);}finally{lock.unlock();System.out.println(Thread.currentThread().getName()+" released lock");}}publicstaticvoidmain(String[]args)throwsInterruptedException{// 测试可重入Threadt1=newThread(ReentrantLockDemo::reentrantMethod,"Thread-Reentrant");t1.start();t1.join();System.out.println("----- Separation Line -----");// 测试中断Threadt2=newThread(()->{try{interruptibleMethod();}catch(InterruptedExceptione){System.out.println(Thread.currentThread().getName()+" was interrupted!");// 中断后,线程会退出,不会持有锁return;}},"Thread-Interruptible");t2.start();// 主线程等待1秒后中断t2TimeUnit.SECONDS.sleep(1);t2.interrupt();t2.join();}}

执行结果

Thread-Reentrant entered reentrantMethod Thread-Reentrant entered innerMethod ----- Separation Line ----- Thread-Interruptible acquired lock in interruptibleMethod Thread-Interruptible released lock Thread-Interruptible was interrupted!

8. 下一篇预告

【Java线程安全实战】⑤ 原子类(Atomic)深度解析:无锁编程(Lock-Free)的终极奥义


9. 经典书籍推荐

  • 《Java并发编程实战》(Java Concurrency in Practice)
    作者: Brian Goetz 等
    这本书被誉为 Java 并发领域的“圣经”。它不仅详细讲解了ReentrantLock、AQS 等核心组件,更重要的是传授了一套完整的并发设计思想和最佳实践。尽管出版较早,但其核心原理至今仍是金科玉律,是每一位 Java 工程师的必读书目。

  • 《Java并发编程的艺术》
    作者: 方腾飞, 魏鹏, 程晓明
    这是一本国人撰写的优秀著作,对ReentrantLock、AQS、ConcurrentHashMap等 JUC 组件的源码剖析极为深入,非常适合希望从源码层面理解 Java 并发机制的读者。


参考链接

  1. ReentrantLock——可重入锁的实现原理 - 内在的天空 - CSDN博客
  2. Java中的公平锁和非公平锁实现详解
  3. ReentrantLock实现原理-盗帅_tim-博客园

10. 前文参考:

  • 【Java线程安全实战】① 从ArrayList并发翻车说起:2025年主流线程安全集合全景图解
  • 【Java线程安全实战】② ConcurrentHashMap 源码深度拆解:如何做到高性能并发?
  • 【Java线程安全实战】③ ThreadLocal 源码深度拆解:如何做到线程隔离?
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/14 1:25:44

【专家级架构设计】:基于Kafka Streams的反应式微服务适配实践

第一章:反应式微服务架构的演进与挑战 随着分布式系统复杂性的不断提升,传统的同步阻塞式微服务架构在高并发、低延迟场景下逐渐暴露出性能瓶颈。反应式微服务架构应运而生,它基于响应式编程模型,强调非阻塞、异步消息传递和弹性伸…

作者头像 李华
网站建设 2026/4/13 10:11:45

【Java双签名安全架构】:深入解析ECDSA+ML-DSA混合签名实战方案

第一章:Java双签名安全架构概述在现代软件分发与安全验证体系中,Java双签名机制作为一种增强代码完整性和来源可信度的技术方案,逐渐被广泛应用于企业级应用和开源项目中。该架构通过结合两种不同签名算法或密钥体系,对JAR文件进行…

作者头像 李华
网站建设 2026/4/9 21:19:53

火山监测预警:地质公园安装VoxCPM-1.5-TTS-WEB-UI熔岩流动提醒

火山监测中的AI语音革命:当熔岩预警“开口说话” 在夏威夷基拉韦厄火山边缘的游客步道旁,一块电子屏突然闪烁红光,紧接着一个沉稳而清晰的声音响起:“注意!东南侧地壳出现异常形变,预计90分钟内可能发生熔岩…

作者头像 李华
网站建设 2026/4/10 20:07:31

程序员破案:那些年我们追过的Bug

技术文章大纲:Bug悬案侦破大会引言背景:软件开发中难以定位的Bug对项目的影响目的:通过“侦破大会”形式提升团队协作与问题解决效率亮点:结合技术、工具与思维方法,系统性解决复杂问题经典Bug悬案案例案例1&#xff1…

作者头像 李华
网站建设 2026/4/14 15:28:23

为什么90%的Java故障排查失败?因为你没用对智能日志分析方法

第一章:为什么90%的Java故障排查失败?在Java应用的日常运维中,故障排查本应是开发与运维人员的核心能力,但现实中超过90%的排查尝试最终未能准确定位问题根源。根本原因并非技术复杂,而是方法论缺失与工具误用。缺乏系…

作者头像 李华
网站建设 2026/4/10 13:17:59

【流处理架构升级指南】:为什么90%的系统都忽略了Kafka Streams的反应式适配能力?

第一章:流处理架构升级的必然趋势随着数据生成速度的指数级增长,传统批处理架构已难以满足实时性要求。企业对低延迟、高吞吐的数据处理能力需求日益迫切,推动流处理架构从辅助角色演变为现代数据平台的核心组件。实时性驱动业务变革 金融风控…

作者头像 李华