news 2026/6/11 21:25:55

剥开 AQS 的外衣:ReentrantLock 凭什么这么灵活?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
剥开 AQS 的外衣:ReentrantLock 凭什么这么灵活?

前言:有了 Synchronized,为什么还要造出 ReentrantLock?

在上篇博客中我们提到,经过优化的synchronized已经很强了。但是,JDK 大神 Doug Lea(Java 并发包 JUC 之父) 依然为我们提供了一把神兵利器:ReentrantLock

既然synchronized能自动加锁解锁,为什么大佬还要我们手动去lock()unlock()呢?
因为synchronized太死板了!它不能被打断、不能超时等待、也不能实现公平排队。要想拥有绝对的并发火力和灵活性,我们必须了解 JUC 的镇山之宝 ——AQS (AbstractQueuedSynchronizer)


一、 AQS 到底是个什么鬼?(通俗解释)

AQS 全称:抽象队列同步器。
你不需要被这个高大上的名字吓到,剥开它的外衣,AQS 本质上就是两个东西的结合体:
AQS =volatile int state(状态变量) +双向 FIFO 链表(等候队列)

小白图景解释:

你可以把 AQS 想象成银行办理业务的大厅

  1. state(计数器):就是大厅里那个 VIP 柜台的状态灯。0代表闲置,1代表里面有人正在办理业务。(如果是可重入锁,那个人反复进出,状态就是 2、3、4…)
  2. 双向链表(Node 队列):就是柜台外面的排队等候区座位。如果新来的客户看到柜台(state = 1)有人,大厅保安(AQS机制)就会给他建一张档案卡(Node节点),并安排他坐到椅子上睡觉(线程休眠LockSupport.park()),等前面的人办完业务走了,再去碰醒排在最前面的客户。

绝大部分 Java 并发包里的锁(ReentrantLock、CountDownLatch、Semaphore、读写锁),底层全是靠继承了 AQS 来实现的!只要你懂了 AQS,大半个并发包就被你拿下了。


二、 剖析 ReentrantLock 的“灵活”

ReentrantLock这个名字直译叫可重入锁。所谓的灵活,其实就在于它提供的“公平”与“非公平”的选择权。

1. 公平锁(FairSync)与非公平锁(NonfairSync)

ReentrantLock内部,有两个基于 AQS 的实现类:

  • 非公平锁(默认默认默认!):新来的线程不管三七二十一,一上来直接通过 CAS 尝试把 AQS 里的state改为 1。如果正好前面那个刚出来,锁空出来了,新线程直接插队成功抢走锁!不管等候队列里有多少人在风中发抖。这种粗暴的做法反而效率最高,因为减少了线程唤醒的开销。
  • 公平锁:新来的线程非常有素质。抢之前先看一眼 AQS 的等待队列。如果有任何人在自己前面排队,那就老老实实自己走到队尾去睡觉排队。杜绝了“饿死”现象,但整体吞吐量低

三、 代码深度拆解(逐行注释)

我们用一段日常都在用的代码,来剖析到底底层发生了什么:

importjava.util.concurrent.locks.ReentrantLock;publicclassReentrantLockAqsDemo{// 默认传入 false,或者不传参,就是【非公平锁】// 传入 true,就是【公平锁】privatestaticfinalReentrantLocklock=newReentrantLock(false);publicstaticvoidmain(String[]args){// 创建三个线程来模拟并发抢锁的过程Threadt1=newThread(ReentrantLockAqsDemo::doBusiness,"小白");Threadt2=newThread(ReentrantLockAqsDemo::doBusiness,"小红");t1.start();t2.start();}publicstaticvoiddoBusiness(){// ========== 【AQS 加锁流程触发】 ==========/* 1. 线程如果是非公平模式,第一步直接去强行 CAS 把 AQS.state 从 0 变成 1。 2. 如果成功了,就在 AQS 里记录 ownerThread 为当前线程,美滋滋去执行业务。 3. 如果发现 state 是 1 已经被占了,也没事。看一眼霸占的人是不是自己。如果是自己,就把 state 加 1 (也就是变成了 2)。这叫【可重入】。 4. 如果都被别人占了,得,进入 AQS 的 acquire 方法,被包装成 Node 扔进双向链表的队尾挂起(park)。 */lock.lock();try{System.out.println(Thread.currentThread().getName()+" -> 获取到了ReentrantLock锁,正在执行业务!");// 模拟复杂业务Thread.sleep(2000);}catch(InterruptedExceptione){e.printStackTrace();}finally{// ========== 【AQS 解锁流程触发】 ==========/* 1. 谁加的锁谁才能解!先判断当前线程是不是 AQS 记录的 ownerThread。 2. 紧接着把 AQS 的 state 减 1。 3. 如果 state 减 1 之后变成 0 了,说明锁彻底释放干净了。 4. AQS 会从双向链表的头部,找到第一个真正还在沉睡排队的 Node,去 unpark() 唤醒他。小弟起来干活拿锁了! */System.out.println(Thread.currentThread().getName()+" -> 业务执行完毕,准备释放锁...");lock.unlock();}}}

四、 面试神仙大乱斗:ReentrantLock vs Synchronized

搞懂了底层之后,面对面试官这道世纪对决题就非常从容了:

  1. 底层原理不同
    • Synchronized是 JVM 层面的关键字,基于对象头的 Mark Word 和系统层面的 Monitor 实现。
    • ReentrantLock是 JDK API 层面的类,纯 Java 代码,完全基于 AQS 这个状态机和等待队列来实现。
  2. 灵活性不同
    • Synchronized只能被动阻塞,死等。非公平。
    • ReentrantLock可以tryLock()(拿不到就马上拉倒返回 false 不死等)、可以带超时等待、更可以设置公平锁与非公平锁,甚至可以通过Condition定制化等待唤醒通道。
  3. 释放方式不同
    • Synchronized执行完大括号,或者抛出异常时,JVM 会自动帮你释放锁
    • ReentrantLock必须要程序员自己在finally代码块强制调用unlock()!!(千万不能忘,否则就是坑爹的死锁)

总结:该用哪个?

在 JDK 1.6 之前毫无疑问用 ReentrantLock,因为那时 synchronized 太慢了。但现在官方已经充分优化了 synchronized。日常最常见的同步需求,优先使用synchronized,代码更简洁不容易漏写释放。
当你需要跨越方法的加解锁、试图中断排队线程、或者需要公平机制时,大胆掏出你的重武器 —— ReentrantLock 吧!

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

“分布式最大相关熵卡尔曼滤波”在Signal Processing 2019年发表的研究

分布式最大相关熵卡尔曼滤波 原文 A distributed maximum correntropy Kalman filter 期刊 Signal Processing 2019年那篇 老铁们,今天聊点硬核的——如何在传感器网络里玩转卡尔曼滤波还自带抗干扰Buff。想象一下十几个无人机编队飞行,每个机载传感器都…

作者头像 李华
网站建设 2026/5/18 22:48:15

Java程序员怎么去看源码?

今天看到了一位博主分享自己阅读开源框架源码的心得,看了之后也引发了我的一些深度思考。我们为什么要看源码?我们该怎么样去看源码? 其中前者那位博主描述的我觉得很全了(如下图所示),就不做过多的赘述了&…

作者头像 李华
网站建设 2026/5/18 22:48:14

FFO呆手6.0

# 呆手6.0 使用说明## 一、软件介绍呆手6.0是一款专为QQ自由幻想游戏设计的辅助工具,提供了多种实用功能,包括游戏窗口管理、按键辅助、快捷功能、金币换算、彩玉换算等。本工具仅通过模拟用户输入实现辅助功能,不读取或修改游戏内存数据&…

作者头像 李华
网站建设 2026/5/18 22:48:16

嵌入式点灯原理:从HAL到裸机的LED控制全链路解析

1. 项目概述blink_LED是一个面向嵌入式初学者与教学场景的极简固件示例,其核心目标并非实现复杂功能,而是以最精炼的代码路径,完整呈现从硬件初始化、外设配置、时序控制到物理输出的全链路嵌入式开发闭环。尽管项目名称直白,但其…

作者头像 李华