ReentrantLock 公平锁 非公平锁底层实现原理
目录
一、基础铺垫:ReentrantLock 的核心结构
二、非公平锁(NonfairSync)底层实现
1. 加锁核心流程
2. 非公平锁关键特性
三、公平锁(FairSync)底层实现
1. 加锁核心流程
2. 公平锁关键特性
四、释放锁逻辑(公平 / 非公平完全一致)
五、核心区别总结(底层实现)
总结
ReentrantLock是 Java 中基于 AQS(AbstractQueuedSynchronizer,抽象队列同步器)实现的可重入锁,公平锁和非公平锁的核心区别:线程获取锁的顺序是否严格遵循等待队列的 FIFO(先进先出)规则。
先明确核心结论:
- 非公平锁:新线程来抢锁时,先直接尝试插队抢占锁,抢不到再进入等待队列排队;
- 公平锁:绝对禁止插队,新线程来抢锁时,先检查队列里有没有其他线程在等待,有就直接排队,不尝试抢占。
一、基础铺垫:ReentrantLock 的核心结构
ReentrantLock内部定义了Sync抽象类(继承 AQS),公平锁FairSync、非公平锁NonfairSync都继承自 Sync;- AQS 核心组件:
state:int 变量,0=无锁,>0=持有锁(重入次数);exclusiveOwnerThread:记录当前持有锁的线程;- CLH 等待队列:双向链表,存储抢不到锁的等待线程。
- 可重入特性:同一个线程多次获取锁时,
state累加,释放锁时递减,直到state=0才真正释放锁。
二、非公平锁(NonfairSync)底层实现
核心设计:先插队,失败再排队,追求更高吞吐量(默认锁)。
1. 加锁核心流程
// 非公平锁lock()方法 final void lock() { // 第一步:直接插队!CAS尝试将state从0改为1(抢占锁) if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); // 抢占成功,设置持有线程 else acquire(1); // 抢占失败,走标准AQS获取锁逻辑 } // AQS的acquire方法(非公平/公平锁通用骨架) public final void acquire(int arg) { // 1. 再次尝试获取锁 2. 获取失败则入队等待 if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } // 非公平锁的tryAcquire(重写AQS方法) protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } // 非公平锁核心获取逻辑 final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { // 锁空闲 // 再次插队!CAS抢锁(不检查队列) if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } // 可重入:当前线程已持有锁,state+1 else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; setState(nextc); return true; } return false; // 抢锁失败 }2. 非公平锁关键特性
- 两次插队机会:
- 第一次:
lock()直接 CAS 抢锁; - 第二次:
tryAcquire()发现锁空闲,再次 CAS 抢锁;
- 第一次:
- 不检查等待队列:只要锁空闲,新线程可以直接抢走,不管队列里有没有等待很久的线程;
- 优点:减少线程上下文切换,吞吐量高;缺点:可能导致队列线程 "饥饿"(长时间抢不到锁)。
三、公平锁(FairSync)底层实现
核心设计:不插队,先检查队列,排队获取锁,保证严格的先来先服务。
1. 加锁核心流程
// 公平锁lock()方法(直接走acquire,没有插队CAS) final void lock() { acquire(1); } // 公平锁的tryAcquire(重写AQS方法) protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { // 核心区别:先检查队列!hasQueuedPredecessors()=队列有等待线程 if (!hasQueuedPredecessors() && // 队列空,才允许抢锁 compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } // 可重入逻辑和非公平锁完全一致 else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; setState(nextc); return true; } return false; }2. 公平锁关键特性
- 禁止任何插队:
lock()直接走标准流程,没有 CAS 抢占; - 强制检查队列:
hasQueuedPredecessors()是核心判断:- 如果队列里有其他线程在等待 → 新线程直接入队,不抢锁;
- 只有队列为空 / 自己是队首线程 → 才允许抢锁;
- 优点:无线程饥饿,严格公平;缺点:频繁上下文切换,吞吐量低于非公平锁。
四、释放锁逻辑(公平 / 非公平完全一致)
释放锁没有公平性区别,核心都是:
state递减;state=0时,释放持有线程,唤醒队列中的下一个等待线程。
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; } // 释放锁:state-1,为0时释放锁 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; }五、核心区别总结(底层实现)
| 对比维度 | 非公平锁(NonfairSync) | 公平锁(FairSync) |
|---|---|---|
| 抢锁入口 | lock()先 CAS 插队抢锁 | 直接走acquire(),不插队 |
| 锁空闲时 | 不检查队列,直接 CAS 抢锁 | 必须先检查hasQueuedPredecessors() |
| 等待队列 | 新线程可能跳过队列 | 严格遵守 FIFO 队列顺序 |
| 线程饥饿 | 可能出现 | 不会出现 |
| 吞吐量 | 高 | 低 |
| 底层差异 | 无队列检查 | 多了hasQueuedPredecessors()判断 |
总结
- 底层基石:公平 / 非公平锁都基于AQS实现,核心差异在
tryAcquire()抢锁逻辑; - 非公平锁:插队优先,两次 CAS 抢锁,不检查等待队列,吞吐量更高;
- 公平锁:排队优先,强制检查队列,绝对禁止插队,无饥饿但性能更低;
- 通用特性:可重入、锁释放逻辑、CLH 等待队列、AQS 的
state状态管理完全一致。
