目录
2.1 AQS(AbstractQueuedSynchronizer)框架
在Java并发编程中,ReentrantLock作为synchronized关键字的重要替代方案,提供了更灵活、更强大的线程同步机制。本文将深入剖析ReentrantLock的底层实现原理,帮助开发者彻底掌握这把"可重入锁"的精髓。
一、ReentrantLock核心特性概览
ReentrantLock作为JUC包下的重要组件,具有以下核心特性:
-
可重入性:同一线程可多次获取同一把锁
-
公平性选择:支持公平锁与非公平锁两种模式
-
条件变量:支持多个Condition条件队列
-
中断响应:支持获取锁过程中的中断操作
-
超时机制:支持尝试获取锁的超时设置
二、底层数据结构深度解析
2.1 AQS(AbstractQueuedSynchronizer)框架
ReentrantLock的底层实现完全依赖于AQS框架,其核心组成包括:
// ReentrantLock中的同步器实现
abstract static class Sync extends AbstractQueuedSynchronizer {
// 实现AQS的tryAcquire等方法
}
// 非公平锁实现
static final class NonfairSync extends Sync {}
// 公平锁实现
static final class FairSync extends Sync {}
AQS的核心数据结构:
-
state:volatile int类型,表示锁的状态
-
CLH队列:双向链表实现的等待队列
-
Node节点:封装线程和等待状态
2.2 关键字段内存布局
// AQS中的关键字段
private volatile int state; // 同步状态
private transient volatile Node head; // 队列头
private transient volatile Node tail; // 队列尾
三、加锁过程源码级分析
3.1 非公平锁加锁流程
final void lock() {
if (compareAndSetState(0, 1)) // CAS快速尝试获取锁
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1); // 进入AQS获取流程
}
public final void acquire(int arg) {
if (!tryAcquire(arg) && // 再次尝试获取
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 加入队列
selfInterrupt();
}
3.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;
}
}
// 重入逻辑与非公平锁相同
...
}
四、解锁过程源码级分析
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;
}
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) { // 完全释放
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
五、条件变量实现原理
ConditionObject是AQS的内部类,实现了Condition接口:
public class ConditionObject implements Condition {
private transient Node firstWaiter; // 条件队列头
private transient Node lastWaiter; // 条件队列尾
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter(); // 加入条件队列
int savedState = fullyRelease(node); // 完全释放锁
// ...
}
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first); // 转移节点到同步队列
}
}
六、与synchronized的深度对比
| 特性 | ReentrantLock | synchronized |
|---|---|---|
| 实现级别 | JDK层面(AQS) | JVM层面(Monitor) |
| 锁获取方式 | 显式lock()/unlock() | 隐式进入/退出同步块 |
| 公平性 | 支持公平与非公平 | 仅非公平 |
| 条件等待 | 支持多个Condition | 单一wait/notify |
| 中断响应 | lockInterruptibly()支持 | 不支持 |
| 性能 | 高竞争时表现更好 | 低竞争时优化更好 |
| 锁绑定 | 需要手动释放 | 自动释放 |
七、最佳实践与性能优化
-
锁选择策略:
-
低竞争场景:优先考虑synchronized
-
高竞争场景:使用ReentrantLock的非公平模式
-
需要严格顺序:使用公平锁
-
-
避免常见陷阱:
// 错误示例:忘记释放锁 try { lock.lock(); // 业务代码 } finally { lock.unlock(); // 必须放在finally块中 } -
性能调优技巧:
- 减少锁粒度
- 缩短锁持有时间
- 使用tryLock()避免死锁
八、典型应用场景分析
-
银行转账案例:
public class BankTransfer { private final ReentrantLock lock = new ReentrantLock(); public void transfer(Account from, Account to, int amount) { lock.lock(); try { from.withdraw(amount); to.deposit(amount); } finally { lock.unlock(); } } } -
生产者消费者模式:
public class BoundedBuffer {
private final ReentrantLock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await();
// 入队操作
notEmpty.signal();
} finally {
lock.unlock();
}
}
// 类似地实现take方法...
}
九、常见面试问题深度解答
Q1:AQS为什么选择CLH队列?
A:CLH队列具有以下优势:
-
前驱节点的状态变化可被后续节点感知
-
自旋时只监听前驱节点,减少缓存一致性流量
-
天然的FIFO特性保证公平性
Q2:非公平锁为什么性能更高?
根本原因在于减少了线程切换的开销:
-
新请求线程有机会"插队"直接获取锁
-
避免了唤醒线程的上下文切换成本
-
特别适合锁持有时间短的场景
Q3:如何实现锁的降级?
// 读锁降级示例
public void processData() {
readLock.lock();
if (!update) {
readLock.unlock();
writeLock.lock(); // 必须先获取写锁
try {
if (!update) {
// 准备数据...
update = true;
}
readLock.lock(); // 降级开始:在释放写锁前获取读锁
} finally {
writeLock.unlock(); // 降级完成:此时仍持有读锁
}
}
try {
// 使用数据...
} finally {
readLock.unlock();
}
}
👉 《Java集合框架深度解析:从源码到实战应用》(已完结)
👉 《Java高并发实战:原理、源码与性能调优》(进行中)
📣 互动邀请
如果本文对您有帮助,请不要吝啬您的支持:
👍 点赞 - 您的点赞是我持续创作的最大动力!
🔔 关注 - 获取更多Java核心技术解析和实战技巧
💬 评论 - 有任何问题或建议欢迎留言讨论
🚀 下期预告
接下来我将继续深入讲解:
《Java线程池实现原理深度解析:从设计思想到源码剖析》
7303

被折叠的 条评论
为什么被折叠?



