一句话说透Java里面的ReentrantLock的内部实现

225 阅读3分钟

ReentrantLock 内部实现:大白话解析

ReentrantLock 就像  “智能门禁系统” ,内部通过 AQS(AbstractQueuedSynchronizer)  实现锁的管理,核心是 一个状态变量(state)  和 一个线程等待队列。它的设计目标是 高效、灵活、可重入,支持公平和非公平两种模式。


一、核心组件

  1. AQS(抽象队列同步器)

    • 状态变量(state) :记录锁的持有次数(可重入性)。

      • state=0:锁未被占用。
      • state≥1:锁被占用,数值表示重入次数。
    • 等待队列(CLH队列) :存储等待锁的线程(双向链表结构)。

  2. 锁模式(公平 vs 非公平)

    • 非公平锁(默认) :允许线程插队,提高吞吐量,但可能导致“饥饿”。
    • 公平锁:严格按请求顺序分配锁,避免饥饿,但性能略低。

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

  1. 尝试直接获取锁(CAS操作)

    • 如果 state=0(锁未被占用),通过 CAS 将 state 设为1,并设置当前线程为锁持有者。
    • 成功则直接获取锁,失败则进入队列等待。
  2. 加入等待队列

    • 将线程包装为 Node 节点,通过 CAS 插入队列尾部。
  3. 自旋或阻塞

    • 在队列中自旋检查前驱节点是否是头节点(即是否轮到自己)。
    • 如果是头节点,再次尝试获取锁。
    • 否则,调用 LockSupport.park() 阻塞线程,等待唤醒。

三、解锁流程

  1. 减少重入次数(state减1)

    • 如果 state=0(完全释放锁),唤醒队列中下一个等待线程。
    • 通过 LockSupport.unpark() 唤醒线程,使其重新竞争锁。

四、可重入性实现

  • 记录持有线程:AQS内部保存当前持有锁的线程(exclusiveOwnerThread)。

  • 重入计数:每次重入锁,state 加1;每次释放锁,state 减1,直到 state=0 时完全释放。

    final void lock() {  
        if (compareAndSetState(0, 1)) { // 首次获取锁  
            setExclusiveOwnerThread(Thread.currentThread());  
        } else {  
            acquire(1); // 重入或排队  
        }  
    }  
    

五、公平锁 vs 非公平锁

对比项非公平锁公平锁
获取锁策略直接尝试CAS,不管队列是否有等待线程先检查队列是否有等待线程,有则排队
性能高吞吐量(允许插队)低吞吐量(严格排队)
代码实现ReentrantLock() 默认非公平ReentrantLock(true) 设置为公平

示例代码

// 非公平锁(默认)  
ReentrantLock unfairLock = new ReentrantLock();  

// 公平锁  
ReentrantLock fairLock = new ReentrantLock(true);  

六、关键设计亮点

  1. CAS无锁化竞争:减少线程阻塞,提升并发性能。
  2. 等待队列管理:通过CLH队列避免“惊群效应”(所有线程同时竞争锁)。
  3. 可中断与超时:支持 lockInterruptibly() 和 tryLock(timeout),灵活控制锁的获取。

七、适用场景

  • 高并发竞争:非公平锁提升吞吐量(如秒杀系统)。
  • 严格顺序执行:公平锁保证线程按请求顺序获取锁(如任务调度)。
  • 复杂同步需求:需要可中断、超时、多条件变量的场景(如生产者-消费者模型)。

八、总结

ReentrantLock 的核心是 AQS

  • 通过 state 管理重入次数CLH队列管理等待线程
  • 公平与非公平模式适应不同场景。
  • 比 synchronized 更灵活,但需手动管理锁。

口诀
「ReentrantLock 靠 AQS,状态队列两法宝
非公平锁抢为先,公平锁按顺序来
可重入来可中断,灵活高效并发强!」