当前位置: 首页 > news >正文

深入剖析ReentrantLock底层原理:从AQS到公平锁的源码级解析

📖 摘要

关键词ReentrantLock原理AQS机制公平锁条件变量可重入锁
本文深入解析Java并发包中的ReentrantLock实现,涵盖AQS核心机制、公平/非公平锁差异、Condition条件变量等核心内容,结合源码与流程图,彻底揭示其高并发设计的精妙之处!


📑 目录

  1. ReentrantLock核心特性速览
  2. AQS(AbstractQueuedSynchronizer)架构解析
  3. ReentrantLock的加锁/解锁流程
  4. 公平锁与非公平锁的底层差异
  5. Condition条件变量的实现原理
  6. ReentrantLock vs synchronized深度对比
  7. 性能优化与避坑指南
  8. 高频面试题与总结

1. ReentrantLock核心特性速览

作为java.util.concurrent包的明星类,ReentrantLock相比synchronized具备以下特性:

  • 可中断锁lockInterruptibly()支持响应中断。
  • 公平性选择:构造函数指定公平/非公平策略。
  • 多条件变量:通过newCondition()创建多个等待队列。
  • 尝试获取锁tryLock()支持超时或立即返回。
  • 可重入性:同一线程可重复获取锁。

2. AQS(AbstractQueuedSynchronizer)架构解析

ReentrantLock的底层依赖AQS框架,其核心组成如下:

2.1 AQS数据结构

public abstract class AbstractQueuedSynchronizer {
    // 同步状态(锁的重入次数)
    private volatile int state;
    
    // CLH队列节点(双向链表)
    static final class Node {
        volatile int waitStatus;  // 等待状态(CANCELLED、SIGNAL等)
        volatile Node prev;
        volatile Node next;
        volatile Thread thread;  // 等待线程
        Node nextWaiter;         // 条件队列专用
    }
    
    // 队列头尾指针
    private transient volatile Node head;
    private transient volatile Node tail;
}

2.2 AQS的核心设计思想

  • 模板方法模式:子类(如ReentrantLock.Sync)实现tryAcquire/tryRelease等钩子方法。
  • CLH队列:Craig, Landin, and Hagersten(CLH)锁队列的变种,用于管理竞争线程。
  • 状态管理state字段表示锁的持有次数(可重入性)。

3. ReentrantLock的加锁/解锁流程

3.1 加锁流程(以非公平锁为例)

1. 线程调用lock()方法
2. 直接尝试CAS修改state(快速路径):
   - 成功:设置当前线程为独占所有者(exclusiveOwnerThread)
   - 失败:进入acquire()方法
3. acquire()调用tryAcquire()再次尝试获取锁
4. 若仍失败,将线程封装为Node加入CLH队列
5. 进入自旋,不断检查前驱节点是否为头节点并尝试获取锁
6. 若获取成功,将当前节点设为头节点;否则挂起线程(LockSupport.park())

源码关键路径

final void lock() {
    if (compareAndSetState(0, 1)) // 非公平锁直接抢
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

public final void acquire(int arg) {
    if (!tryAcquire(arg) && 
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

3.2 解锁流程

1. 线程调用unlock()方法
2. 调用tryRelease()减少state值
3. 若state减至0,清空独占所有者线程
4. 唤醒CLH队列中的下一个线程(LockSupport.unpark())

源码关键路径

public void unlock() {
    sync.release(1);
}

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h); // 唤醒后继节点
        return true;
    }
    return false;
}

4. 公平锁与非公平锁的底层差异

4.1 非公平锁(默认策略)

  • 加锁特点:线程可直接插队尝试获取锁(即使队列中有等待线程)。
  • 优势:减少线程切换,吞吐量高。
  • 源码差异
    final boolean nonfairTryAcquire(int acquires) {
        // 直接尝试CAS,不检查队列
    }
    

4.2 公平锁

  • 加锁特点:必须按CLH队列顺序获取锁。
  • 优势:避免线程饥饿。
  • 源码差异
    protected final boolean tryAcquire(int acquires) {
        if (!hasQueuedPredecessors()) { // 检查是否有前驱节点
            // 执行CAS
        }
        // ...
    }
    

4.3 性能对比场景

场景非公平锁公平锁
高竞争短任务更优较差
低竞争长任务接近接近
严格顺序需求不适用必须

5. Condition条件变量的实现原理

5.1 Condition与Object监视器方法的对比

功能ConditionObject监视器
等待队列数量多个单个
唤醒精确性可指定唤醒某个队列只能随机或全部唤醒
超时等待支持awaitNanos()不支持

5.2 Condition内部实现

  • 等待队列:每个Condition对象维护一个单向链表队列。
  • 节点转移
    • await():将节点从CLH锁队列转移到Condition等待队列。
    • signal():将节点从Condition队列移回CLH锁队列。

await()流程

1. 创建Condition节点加入条件队列
2. 完全释放锁(state=0)
3. 进入循环,检查是否在同步队列:
   - 若未在,挂起线程
4. 被唤醒后,尝试重新获取锁

signal()流程

1. 将条件队列的头节点转移到CLH锁队列
2. 修改节点状态为可竞争(SIGNAL)

6. ReentrantLock vs synchronized深度对比

维度ReentrantLocksynchronized
锁实现基于AQS(Java代码)JVM内置(C++实现)
锁释放必须手动unlock()自动释放
公平性支持配置非公平
条件变量多Condition单等待队列
锁中断支持lockInterruptibly()不支持
性能高竞争下更优JDK6后优化接近
锁绑定灵活(可跨方法)方法或代码块

7. 性能优化与避坑指南

7.1 正确使用姿势

ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
    // 临界区代码
} finally {
    lock.unlock(); // 必须放在finally块!
}

7.2 常见问题与解决方案

  • 死锁:按固定顺序获取多个锁。
  • 锁泄漏:确保所有路径都调用unlock()
  • 过度竞争:缩小锁粒度或使用读写锁(ReentrantReadWriteLock)。

7.3 监控工具

  • Jstack:查看线程锁持有情况。
  • Arthas:实时监控锁竞争状态。

8. 高频面试题与总结

高频面试题:

  1. Q:AQS为什么用双向链表?
    A:便于处理取消节点(CANCELLED状态),快速移除失效节点。

  2. Q:ReentrantLock如何实现可重入?
    A:通过state计数器记录重入次数,释放时递减至0才完全释放。

  3. Q:非公平锁是否会导致饥饿?
    A:理论上可能,但实际高吞吐场景中概率极低。

核心总结:

  • AQS是核心:理解CLH队列与state状态流转。
  • 公平性选择:根据场景权衡吞吐量与公平性。
  • 条件变量:复杂等待/通知场景的首选工具。

相关文章:

  • 游戏引擎学习第189天
  • Selenium测试框架快速搭建
  • AILabel标注工具指南(二):禁止图片外标注
  • 技术速递|为 .NET 的 AI 评估解锁新的可能性
  • 跟着尚硅谷学vue-day1
  • Debian ubuntu源
  • 在Electron+Vue应用中实现文件自动监视与更新功能
  • QT路径获取
  • Spark2 之 qualification-tool
  • 解释时间复杂度 O() 表示法,如何评估算法效率?
  • MATLAB中getfield函数用法
  • [GESP202503 C++一级题解]:B4257:图书馆里的老鼠
  • 聚焦交易能力提升!EagleTrader 模拟交易系统打造交易成长新路径
  • Mac: 运行python读取CSV出现 permissionError
  • ARCGIS PRO SDK VB2022 图层要素类类型判断
  • 【RocketMQRocketMQ Dashbord】Springboot整合RocketMQ
  • 大模型 API 调用中的流式输出与非流式输出全面对比:原理、场景与最佳实践
  • 【web应用安全】关于web应用安全的几个主要问题的思考
  • 【Uni-App】嵌入悬浮球全局组件的详细教程和防踩坑点
  • 高校动作捕捉实训室:赋能高校高水平动画专业人才培养
  • 上海一隧道成“王家卫风”网红拍照点?交管部门已专项整治,一人被处罚
  • 区域、学校、课堂联动,上海浦东让AI素养培育贯穿基础教育全学段
  • 哪都“差一点”的《歌手2025》,还能爆吗?
  • 凤阳文旅局长回应鼓楼瓦片脱落:楼宇是否属于文物?施工经费用在何处?
  • 习近平:坚定信心推动高质量发展高效能治理,奋力谱写中原大地推进中国式现代化新篇章
  • 22国外长联合声明:要求以方立即允许全面恢复对加沙援助