在 Java 里,ReentrantLock借助 AQS(AbstractQueuedSynchronizer)来实现公平锁和非公平锁,不过它们的实现方式存在差异。下面为你详细介绍这两种锁的底层实现原理。
非公平锁的实现原理
非公平锁在获取锁时,会先尝试通过 CAS 操作来抢占锁资源。若抢占成功,就直接获取到锁;只有当抢占失败时,才会进入 AQS 队列等待。其具体实现步骤如下:
-
尝试抢占锁:当线程调用
lock()方法时,非公平锁会马上尝试利用 CAS 操作将 state 从 0 设置为 1。若设置成功,就表明线程获取到了锁。 -
锁重入处理:要是当前线程已经持有了该锁,再次获取锁时,只需把 state 的值加 1,以此记录锁的重入次数。
-
进入等待队列:若 CAS 操作失败,说明锁已被其他线程持有,此时线程会被封装成 Node 节点,加入到 AQS 的 CLH 队列尾部,进入等待状态。
下面是简化后的代码实现:
final void lock() {
if (compareAndSetState(0, 1)) // 第一步:直接尝试CAS抢占锁
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1); // 调用AQS的acquire方法
}
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) {
if (compareAndSetState(0, acquires)) { // 再次尝试CAS
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) { // 处理锁重入
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
公平锁的实现原理
公平锁获取锁的过程严格遵循请求的先后顺序,也就是按照 AQS 队列的顺序来获取锁。具体实现步骤如下:
-
检查队列状态:当线程调用
lock()方法时,公平锁会先查看 AQS 队列中是否有其他线程在等待。若队列不为空,当前线程就会直接进入队列尾部等待。 -
尝试获取锁:只有当队列中没有其他等待线程,或者当前线程是队列的头节点时,才会尝试通过 CAS 操作获取锁。
-
锁重入处理:和非公平锁一样,公平锁也需要处理锁的重入情况。
下面是简化后的代码实现:
final void lock() {
acquire(1); // 直接调用AQS的acquire方法
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 关键区别:先检查队列中是否有前驱节点,再尝试CAS
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) { // 处理锁重入
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
关键差异总结
| 对比项 | 公平锁 | 非公平锁 |
|---|---|---|
| 获取锁的顺序 | 严格按照请求顺序,依赖 AQS 队列 | 不保证顺序,可能后请求的线程先获取到锁 |
| 性能 | 由于要频繁检查队列状态,性能相对较低 | CAS 操作的成功率较高,性能相对较好 |
| 饥饿问题 | 不会出现线程饥饿情况 | 可能会导致部分线程长时间无法获取到锁 |
| 实现复杂度 | 较高,需要维护队列的顺序 | 较低,无需额外处理队列顺序 |
综上所述,公平锁保证了锁获取的公平性,但这是以牺牲一定的性能为代价的;而非公平锁虽然可能会造成线程饥饿,不过在大多数情况下,它的性能表现更优。