AQS 默认非公平加锁策略¶
一、公平 vs 非公平¶
- 公平锁:按队列顺序,先到先得。
- 非公平锁:新线程可以直接抢锁,不管队列里有没有人等。
ReentrantLock 默认非公平。
二、非公平 lock()¶
final void lock() {
if (compareAndSetState(0, 1)) // 直接抢!
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
公平锁不会在开头 CAS,而是直接进 acquire:
三、非公平的 tryAcquire¶
final boolean nonfairTryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
} else if (current == getExclusiveOwnerThread()) {
setState(c + acquires); // 重入
return true;
}
return false;
}
四、为什么非公平性能好¶
假设线程 A 持有锁,线程 B 在队列里等,线程 C 新来:
- 公平:C 必须等 B 拿到锁、用完、释放,才能拿。B 从挂起到唤醒要切换线程,有延迟。
- 非公平:C 在 B 还没完全唤醒时就抢到了锁,减少了线程切换开销。
非公平可能让队列里的线程"饿死",但吞吐更高。
五、ReentrantLock 选择¶
六、源码对比¶
| 非公平 | 公平 | |
|---|---|---|
| lock() | 先 CAS 抢 | 直接 acquire |
| tryAcquire | 不检查队列 | 检查 hasQueuedPredecessors |
| 性能 | 高 | 低 |
| 公平性 | 可能饥饿 | FIFO |
面试要点
- 非公平默认:减少线程挂起/唤醒的开销。
- 公平锁保证不饥饿,但吞吐低。
- 大多数场景用非公平。