news 2026/2/10 12:04:39

《Java 并发编程的艺术》| AQS底层实现 与 ReentrantLock(可重入 / 公平性)核心解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
《Java 并发编程的艺术》| AQS底层实现 与 ReentrantLock(可重入 / 公平性)核心解析

摘要本篇文章围绕 Java 并发编程中的核心内容,梳理了 Lock 接口与 synchronized 的差异,详解了队列同步器AQS的实现机制、ReentrantLock的可重入特性、公平锁与非公平锁的实现。

第5章 Java 中的锁

5.1 Lock接口

"它提供了与 synchronized 关键字类似的同步功能,只是在使用时需要显式地获取和释放锁。虽然它缺少了(通过 synchronized 块或者方法所提供的)隐式获取释放锁的便捷性,但是却拥有了锁获取与释放的可操作性、可中断的获取锁以及超时获取锁等多种 synchronized 关键字所不具备的同步特性。"

这句话精准点明了Lock接口(以ReentrantLock为例)与synchronized的核心差异和价值。


ReentrantLock(Lock 接口实现)与 synchronized 的详细区别

特性ReentrantLock(Lock 接口实现)synchronized
获取 / 释放方式显式调用 lock() / unlock(),必须在 finally 中释放锁,灵活性高隐式获取 / 释放(JVM 自动管理),简化了同步管理,但无法灵活控制释放时机
可中断获取锁支持 lockInterruptibly(),在等待获取锁时可响应中断,避免线程永久阻塞不支持,一旦进入等待状态,只能等锁释放或线程终止
超时获取锁支持 tryLock(long time, TimeUnit unit),在指定时间内未获取到锁则返回 false,避免无限等待不支持,只能无限等待
非阻塞获取锁支持 tryLock(),尝试获取锁,获取失败立即返回 false,不会阻塞不支持,获取不到锁就会阻塞
锁获取顺序可公平 / 非公平锁,公平锁严格遵循 FIFO 顺序,非公平锁默认插队抢占非公平锁,默认不保证顺序
重入性支持,重入次数通过 getHoldCount() 查询支持,但无法查询重入次数
扩展性可灵活控制锁的获取 / 释放顺序,适合复杂场景(如多锁顺序控制)锁的获取 / 释放顺序固化,扩展性差

三、核心差异总结

  1. 灵活性ReentrantLock以 "显式操作" 为代价,换取了synchronized不具备的可中断、超时获取、精准唤醒等特性,适合复杂并发场景;

  2. 易用性synchronized由 JVM 自动管理,无需手动释放,降低了死锁风险,适合简单同步场景;

  3. 性能:在高并发场景下,ReentrantLock的性能通常优于synchronized(尤其是非公平锁);

  4. 功能丰富度ReentrantLock支持公平锁、多个Condition、锁状态查询等高级功能,是synchronized的超集。

简单来说:

  • 如果你的场景简单,用synchronized更省心;

  • 如果需要处理复杂的锁控制(如超时等待、可中断、精准唤醒),用ReentrantLock更强大。

5.2 队列同步器

"队列同步器 AbstractQueuedSynchronizer(以下简称同步器),是用来构建锁或者其他同步组件的基础框架,它使用了一个 int 成员变量表示同步状态,通过内置的 FIFO 队列来完成资源获取线程的排队工作。"

这句话是整段内容的核心,直接点明了 AQS 的本质、核心实现机制与定位。它清晰解释了 AQS 是什么(基础框架)、如何工作(用int维护状态、用 FIFO 队列管理线程排队)、能解决什么问题(构建各种同步组件)。

5.2.1 队列同步器的接口

同步器的主要使用方式是继承,子类通过继承同步器并实现它的抽象方法来管理同步状态,在抽象方法的实现过程中免不了要对同步状态进行更改,这时就需要使用同步器提供的 3 个方法(getState ()、setState (int newState) 和 compareAndSetState (int expect,int update))来进行操作,因为它们能够保证状态的改变是安全的。

  • 使用方式:AQS 采用 "模板方法" 设计模式,需要子类继承它,并实现特定的抽象方法(如独占式获取 / 释放、共享式获取 / 释放)来定义同步逻辑。

  • 状态操作:修改同步状态时,必须使用 AQS 提供的getState()setState()compareAndSetState()方法,这些方法能保证在多线程下对int状态的修改是线程安全的,尤其是compareAndSetState()是基于 CAS 实现的,能避免并发冲突。

表 5-3 列出了 AQS 中需要子类重写的核心方法,这些方法是实现自定义同步逻辑的关键,分为独占式共享式两类:

1.独占式方法(同一时刻仅一个线程持有)

  • protected boolean tryAcquire(int arg)

    • 作用:尝试独占式获取同步状态。

    • 逻辑:查询当前同步状态,判断是否符合获取条件。若符合,就用compareAndSetState()(CAS)修改状态,成功返回true,失败返回false

    • 典型应用:ReentrantLock中实现 "加锁" 的核心逻辑。

  • protected boolean tryRelease(int arg)

    • 作用:尝试独占式释放同步状态。

    • 逻辑:修改同步状态,释放后会唤醒队列中等待的线程,让它们有机会获取同步状态。

    • 典型应用:ReentrantLock中实现 "解锁" 的核心逻辑。


2.共享式方法(同一时刻允许多个线程持有)

  • protected int tryAcquireShared(int arg)

    • 作用:尝试共享式获取同步状态。

    • 逻辑:返回值为>=0表示获取成功;返回值<0表示获取失败。

    • 典型应用:CountDownLatch中 "等待" 的逻辑、ReentrantReadWriteLock的读锁获取逻辑。

  • protected boolean tryReleaseShared(int arg)

    • 作用:尝试共享式释放同步状态。

    • 逻辑:释放同步状态,成功返回true,并会唤醒所有等待的共享式线程。

    • 典型应用:CountDownLatch中 "计数减一" 的逻辑、ReentrantReadWriteLock的读锁释放逻辑。


共享模式和独占模式的核心区别,在于获取成功后的行为,而不是获取前的检查:

  • 独占模式:线程 B 成功获取后,只会把自己设为新的头节点,等待下一次释放时再唤醒它的后继。

  • 共享模式:线程 B 成功获取后,除了把自己设为新的头节点,还会继续向后传播唤醒它的后继节点(线程 C),以此让多个线程能同时获取共享资源。

5.2.2 队列同步器的实现分析

1.同步队列的核心作用

  • AQS 内部维护一个FIFO 双向队列,这是它实现线程同步的基础。

  • 当线程尝试获取同步状态失败时,会被包装成一个Node节点,加入队列尾部并阻塞。

  • 当持有同步状态的线程释放资源时,会唤醒队列头部的线程,让它再次尝试获取同步状态。


  1. 节点(Node)的结构与作用

  • 每个Node节点保存了以下关键信息:

    • 线程引用:指向获取同步状态失败的线程。

    • 等待状态:表示线程在队列中的等待状态(如CANCELLEDSIGNALCONDITION等)。

    • 前驱 / 后继指针:用于维护队列的双向链表结构,支持节点的插入与移除。

  • 这个设计确保了线程能以有序、安全的方式排队等待,避免了 "饥饿" 问题。


  1. 完整工作流程

  • 线程获取状态失败→ 被包装成Node加入队列尾部,并阻塞。

  • 线程释放状态→ 唤醒队列头部的线程。

  • 被唤醒的线程→ 再次尝试获取同步状态,成功则从队列中移除,失败则继续等待。

当一个线程成功地获取了同步状态(或者锁),其他线程将无法获取到同步状态,转而被构造成为节点并加入到同步队列中,而这个加入队列的过程必须要保证线程安全,因此同步器提供了一个基于 CAS 的设置尾节点的方法:compareAndSetTail(Node expect, Node update)

1.同步队列的基本结构

  • AQS 内部维护一个FIFO双向链表作为同步队列,包含两个核心引用:

    • head:指向队列的头节点(代表当前持有同步状态的线程)。

    • tail:指向队列的尾节点(代表最后一个进入队列等待的线程)。

  • 每个节点(Node)包含prev(前驱指针)和next(后继指针),用于维护链表的双向关联。


2.节点入队的核心逻辑

  • 触发条件:当线程尝试获取同步状态失败时,会被包装成一个Node节点,准备加入队列。

  • 线程安全保证:为了在多线程并发入队时保证队列的一致性,AQS 使用compareAndSetTail这个基于 CAS 的方法来设置新的尾节点。

    • 它需要传入两个参数:expect(当前线程认为的尾节点)和update(当前要加入的新节点)。

    • 只有当 "预期的尾节点" 和实际尾节点一致时,才会将新节点设置为尾节点,并建立前驱关联,从而保证入队操作的原子性。

  • 头节点更新:当队列头部的线程成功获取同步状态后,会调用setHead方法将该节点设置为新的头节点,原头节点会被移除。


3.完整流程梳理

  1. 线程 A 成功获取同步状态,成为工作线程。

  2. 线程 B、C 等后续线程获取失败,被包装为Node节点。

  3. 这些节点通过compareAndSetTail方法,以 CAS 安全地添加到队列尾部。

  4. 当线程 A 释放同步状态时,会唤醒头节点的后继节点(线程 B),让它尝试获取同步状态。

  5. 线程 B 成功获取后,通过setHead成为新的头节点,等待队列继续向后推移。

  1. 线程 A 成功获取同步状态,成为工作线程。

  2. 线程 B、C 等后续线程获取失败,被包装为Node节点,调用enq方法尝试入队。

  3. enq方法的 死循环(自旋)中,线程通过compareAndSetTail以 CAS 方式安全地将自己设置为新的尾节点,只有设置成功才会退出循环,否则会一直尝试,从而将并发入队的请求 "串行化"。

  4. 节点成功入队后,进入自旋等待阶段:每个节点会不断检查自己的前驱节点是否为头节点,以此判断自己是否有机会获取同步状态。

  5. 当线程 A 释放同步状态时,会唤醒头节点的后继节点(线程 B)。

  6. 线程 B 被唤醒后,在自旋中检查到自己的前驱是头节点,便会尝试获取同步状态。

  7. 线程 B 成功获取同步状态后,调用setHead方法将自己设置为新的头节点,原头节点被移除,等待队列继续向后推移。

  8. 如果线程 B 获取失败,会再次进入阻塞状态,等待下一次被唤醒。

5.3 重入锁 ReentrantLock

"该方法增加了再次获取同步状态的处理逻辑:通过判断当前线程是否为获取锁的线程来决定获取操作是否成功,如果是获取锁的线程再次请求,则将同步状态值进行增加并返回 true,表示获取同步状态成功。"

这句话精准点出了ReentrantLock可重入特性的核心实现逻辑,是理解这段代码的关键。

final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); // 1. 锁未被任何线程持有 if (c == 0) { if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } // 2. 锁已被当前线程持有(可重入逻辑) else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires;if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } // 3. 锁被其他线程持有,获取失败 return false; }

为什么叫可重入锁:如果当前线程已经是锁的拥有者(current == getExclusiveOwnerThread()),则直接增加同步状态值(nextc = c + acquires),表示重入次数加一。

"如果该锁被获取了 n 次,那么前 (n-1) 次tryRelease(int releases)方法必须返回 false,而只有同步状态完全释放了,才能返回 true。"

这句话精准概括了ReentrantLock可重入特性在释放锁时的核心逻辑,是理解这段代码的关键。

protected final boolean tryRelease(int releases) { // 1. 计算释放后的同步状态 int c = getState() - releases; // 2. 检查当前线程是否是锁的持有者 if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; // 3. 只有当状态值减到0时,才真正释放锁 if (c == 0) { free = true; setExclusiveOwnerThread(null); } // 4. 更新同步状态 setState(c); // 5. 返回是否完全释放 return free; }

只有当状态值c减到 0 时,才会将锁的拥有者设为null,并将free标记为true

这意味着,对于一个被重入了 n 次的锁,前n-1次调用tryRelease都会返回false,只有第 n 次才会返回true,真正释放锁。

5.3.1 公平锁和非公平锁

"公平性与否是针对获取锁而言的,如果一个锁是公平的,那么锁的获取顺序就应该符合请求的绝对时间顺序,也就是 FIFO。"

这句话精准定义了公平锁的核心原则,是理解公平与非公平锁区别的关键。

核心改写位置:第 6 步「尝试获取同步状态」

AQS 的 8 步通用流程中,前 5 步(入队、自旋等待、唤醒)和后 2 步(设置头节点 / 重新阻塞)完全不变,唯一的差异在线程被唤醒后 "尝试获取同步状态" 的判断逻辑:

步骤公平锁(FairSync)非公平锁(NonfairSync)
6. 尝试获取同步状态调用 FairSync.tryAcquire(): 1. 先检查 hasQueuedPredecessors()(队列中是否有更早请求的线程); 2. 只有队列中无前置线程,才通过 CAS 尝试获取状态。

调用 NonfairSync.tryAcquire()(实际是 nonfairTryAcquire()): 1. 不检查队列,直接通过 CAS 尝试抢占同步状态;2. 抢占失败后,才走队列等待逻辑。

protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { // 公平锁的关键:检查队列中是否有前驱节点 if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } // 可重入逻辑(与非公平锁一致) else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }

公平锁的核心实现:

  • 公平锁的关键在于!hasQueuedPredecessors()这个条件判断:

    • hasQueuedPredecessors()方法会检查同步队列中是否有比当前线程更早请求锁的线程。

    • 只有当队列中没有前驱线程(即当前线程是队列第一个)时,才会尝试通过 CAS 获取锁。

  • 这个判断保证了锁的获取顺序严格遵循请求的时间顺序,实现了公平性。


非公平锁的获取逻辑

非公平锁是ReentrantLock默认实现,它的核心特点是允许 "插队",即新线程可以直接尝试抢占锁,而无需等待队列中的线程。

非公平锁获取流程

  1. 直接抢占:当锁可用时,新线程会直接通过CAS尝试设置同步状态,成功则立即获取锁,无需进入队列。

  2. 抢占失败入队:如果抢占失败,才会被包装成Node节点加入队列,后续逻辑与公平锁一致。

  3. 唤醒后抢占:当持有锁的线程释放时,头节点的后继线程会被唤醒。但此时如果有新线程也在尝试获取锁,新线程可以和被唤醒的线程一起竞争,这就可能导致队列中的线程 "饥饿"。

5.3.2 公平与非公平锁的实现

核心逻辑总结

  1. 构造阶段:绑定 Sync 实现:当你用new ReentrantLock(true/false)创建锁时,本质是给ReentrantLocksync成员变量赋值:

    1. truesync = new FairSync()(公平锁)

    2. false/无参sync = new NonfairSync()(非公平锁)这一步就 "定死" 了后续所有获取锁的逻辑。

  2. 调用阶段:执行对应重写的 tryAcquire:当调用lock()最终触发tryAcquire时:

    1. 如果是FairSync,就执行它重写的tryAcquire(带hasQueuedPredecessors()检查);

    2. 如果是NonfairSync,就执行它重写的tryAcquire(调用nonfairTryAcquire,无队列检查)。


ReentrantLock的构造函数正是通过选择实例化FairSyncNonfairSync,来决定锁是公平还是非公平:ReentrantLock构造函数源码

// 默认构造:非公平锁 public ReentrantLock() { sync = new NonfairSync(); } // 带参数构造:true为公平锁,false为非公平锁 public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }

ReentrantLock的公平与非公平锁,是通过继承Sync这个抽象内部类,并实现不同的tryAcquire方法来区分的。

// 抽象基类,封装了AQS的通用逻辑 abstract static class Sync extends AbstractQueuedSynchronizer { // 释放锁的逻辑(公平与非公平锁共享) protected final boolean tryRelease(int releases) { /* ... */ } } // 非公平锁实现 static final class NonfairSync extends Sync { protected final boolean tryAcquire(int acquires) { // 直接尝试CAS抢占,失败再检查可重入 return nonfairTryAcquire(acquires); } } // 公平锁实现 static final class FairSync extends Sync { protected final boolean tryAcquire(int acquires) { // 先检查队列中是否有前驱,没有才尝试CAS final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } // 可重入逻辑 else if (current == getExclusiveOwnerThread()) { /* ... */ } return false; } }

关键区别

  • 非公平锁(NonfairSync):重写tryAcquire方法时,直接调用nonfairTryAcquire,核心是无检查直接 CAS 抢占。

  • 公平锁(FairSync):重写tryAcquire方法时,增加了hasQueuedPredecessors()检查,确保只有队列中没有前驱线程时,才会尝试 CAS。


恭喜你学习完本节内容!✿

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/8 13:09:00

​Android 基础入门教程​Handler消息传递机制浅析

3.3 Handler消息传递机制浅析 分类 Android 基础入门教程 本节引言 前两节中我们对Android中的两种事件处理机制进行了学习&#xff0c;关于响应的事件响应就这两种&#xff1b;本节给大家讲解的 是Activity中UI组件中的信息传递Handler&#xff0c;相信很多朋友都知道&…

作者头像 李华
网站建设 2026/2/7 17:34:24

HTML AI 编程助手AI

HTML AI 编程助手 AI 技术的飞速发展正在深刻改变开发者的工作方式。在 HTML 网页开发中&#xff0c;我们常常被大量细微却高频的重复操作降低效率。因此&#xff0c;AI 的出现可以改变我们的编程方式与提高效率。 AI 对我们来说就是一个可靠的编程助手&#xff0c;给我们提供…

作者头像 李华
网站建设 2026/2/9 16:35:10

在吴忠,遇见一位懂你的羽毛球教练:韩宁波与他的科学训练之道

在吴忠&#xff0c;提起羽毛球&#xff0c;有一个名字和一种训练理念正被越来越多的爱好者所认可——国家二级运动员韩宁波教练与他所在的码上羽毛球俱乐部。这里没有玄妙的“速成秘籍”&#xff0c;有的是一位专业教练对运动规律的深刻理解&#xff0c;以及一套将热情与科学融…

作者头像 李华
网站建设 2026/2/5 10:03:58

羽球成长新体验:当专业教练遇见智能系统

韩宁波教练站在场地中央&#xff0c;手中的球拍仿佛被注入了灵魂&#xff0c;每一次挥动都精准计算过落点。作为国家二级运动员&#xff0c;他深知羽毛球运动的精妙所在——不仅是力量的爆发&#xff0c;更是节奏、技巧与智慧的融合。 在吴忠码上羽毛球俱乐部&#xff0c;他正…

作者头像 李华
网站建设 2026/2/7 9:04:43

在吴忠,与专业教练和智能科技一同成长:开启你的羽毛球精进之旅

在吴忠&#xff0c;有一处备受羽毛球爱好者青睐的天地——吴忠码上羽毛球俱乐部。这里不仅活跃着一位重要的引路人&#xff1a;国家二级运动员韩宁波教练&#xff0c;更在悄然融入现代科技&#xff0c;让羽球训练变得更为清晰、高效。我们致力于提供专业的指导与贴心的支持&…

作者头像 李华
网站建设 2026/2/10 3:15:47

光伏发电MPPT+能量管理(内含报告两万字)simulink仿真实现(设计源文件+万字报告+讲解)(支持资料、图片参考_相关定制)_文章底部可以扫码

光伏发电MPPT能量管理(内含报告两万字)simulink仿真实现(设计源文件万字报告讲解)&#xff08;支持资料、图片参考_相关定制&#xff09;_文章底部可以扫码主要分为五章&#xff0c;在第一章主要介绍了该论文的研究背景与意义&#xff0c;以及混合储能技术在国内外的研究现状&a…

作者头像 李华