ReentrantLock 内部实现:大白话解析
ReentrantLock 就像 “智能门禁系统” ,内部通过 AQS(AbstractQueuedSynchronizer) 实现锁的管理,核心是 一个状态变量(state) 和 一个线程等待队列。它的设计目标是 高效、灵活、可重入,支持公平和非公平两种模式。
一、核心组件
-
AQS(抽象队列同步器)
-
状态变量(state) :记录锁的持有次数(可重入性)。
state=0:锁未被占用。state≥1:锁被占用,数值表示重入次数。
-
等待队列(CLH队列) :存储等待锁的线程(双向链表结构)。
-
-
锁模式(公平 vs 非公平)
- 非公平锁(默认) :允许线程插队,提高吞吐量,但可能导致“饥饿”。
- 公平锁:严格按请求顺序分配锁,避免饥饿,但性能略低。
二、加锁流程(以非公平锁为例)
-
尝试直接获取锁(CAS操作)
- 如果
state=0(锁未被占用),通过 CAS 将state设为1,并设置当前线程为锁持有者。 - 成功则直接获取锁,失败则进入队列等待。
- 如果
-
加入等待队列
- 将线程包装为 Node 节点,通过 CAS 插入队列尾部。
-
自旋或阻塞
- 在队列中自旋检查前驱节点是否是头节点(即是否轮到自己)。
- 如果是头节点,再次尝试获取锁。
- 否则,调用
LockSupport.park()阻塞线程,等待唤醒。
三、解锁流程
-
减少重入次数(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);
六、关键设计亮点
- CAS无锁化竞争:减少线程阻塞,提升并发性能。
- 等待队列管理:通过CLH队列避免“惊群效应”(所有线程同时竞争锁)。
- 可中断与超时:支持
lockInterruptibly()和tryLock(timeout),灵活控制锁的获取。
七、适用场景
- 高并发竞争:非公平锁提升吞吐量(如秒杀系统)。
- 严格顺序执行:公平锁保证线程按请求顺序获取锁(如任务调度)。
- 复杂同步需求:需要可中断、超时、多条件变量的场景(如生产者-消费者模型)。
八、总结
ReentrantLock 的核心是 AQS:
- 通过 state 管理重入次数,CLH队列管理等待线程。
- 公平与非公平模式适应不同场景。
- 比
synchronized更灵活,但需手动管理锁。
口诀:
「ReentrantLock 靠 AQS,状态队列两法宝
非公平锁抢为先,公平锁按顺序来
可重入来可中断,灵活高效并发强!」