东方市网站建设_网站建设公司_表单提交_seo优化
2026/1/2 14:37:43 网站建设 项目流程

📖目录

  • 前言
  • 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 源码深度拆解:如何做到线程隔离?

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询