前言
为了方便理解Java Lock的公平锁和非公平锁,这里再回顾下Java Lock相关的类继承结构:
flowchart TD
%% 接口层
Lock[Lock接口] --> RL[ReentrantLock]
Lock --> RLock[ReadLock]
Lock --> WLock[WriteLock]
RWL[ReadWriteLock接口] --> RWLImpl[ReentrantReadWriteLock]
RWLImpl --> RLock
RWLImpl --> WLock
Cond[Condition接口] --> CondObj[ConditionObject]
%% AQS层
AQS[AQS] --> Sync[Sync]
AQS --> RWSync[ReadWriteSync]
Sync --> FSync[FairSync]
Sync --> NFSync[NonfairSync]
%% 组合关系
RL -.-> Sync
RWLImpl -.-> RWSync
RLock -.-> RWSync
WLock -.-> RWSync
Sync -.-> CondObj
RWSync -.-> CondObj
style Lock fill:#f9f
style RWL fill:#f9f
style Cond fill:#f9f
style AQS fill:#ccf
style RL fill:#cfc
style RWLImpl fill:#cfc
style RLock fill:#cfc
style WLock fill:#cfc
本文主要分析Java Lock的公平锁和非公平锁定义、源码实现和特殊说明。
什么是公平锁和非公平锁
这两种锁主要针对 获取锁的顺序 而言,主要常见于 ReentrantLock 的实现中。
公平锁
- 定义:公平锁是指多个线程按照申请锁的顺序来获取锁。线程直接加入等待队列,队列遵循 FIFO(先进先出)原则。当一个线程尝试获取锁时,它会先检查队列中是否有其他线程在等待。如果有,它就会乖乖排到队尾,不会尝试“插队”。
- 优点:绝对的公平,不会出现线程“饥饿”现象(即某个线程一直拿不到锁)。
- 缺点:性能相对较低,吞吐量不如非公平锁。
非公平锁
- 定义:非公平锁是指多个线程获取锁的顺序并不一定按照申请锁的顺序。允许线程“插队”。当一个线程尝试获取锁时,无论队列中是否有等待线程,它都会先尝试直接抢占锁(CAS操作)。如果抢占成功,就获取锁;如果失败,才会进入队列排队。
- 优点:性能高于公平锁。因为减少了线程挂起和唤醒的开销,提高了系统的整体吞吐量。
- 缺点:可能导致线程“饥饿”,即某个线程运气不好,总是抢不到锁,一直在等待。
为什么非公平锁性能更好?
在公平锁机制下,线程A释放锁后,需要唤醒队列中的线程B。在B被唤醒并真正获取锁的这段时间间隔内,如果有新线程C来了,公平锁会让C排队等待。 而在非公平锁机制下,C可以直接“插队”获取锁。这省去了C排队、挂起和后续唤醒B的开销,充分利用了CPU的时间片。
关键源码
Java的锁机制在 java.util.concurrent.locks 包中,其核心实现基于一个抽象框架:
Lock 接口 (例如 ReentrantLock)
|
| 依赖
↓
Sync 内部类 (继承自 AbstractQueuedSynchronizer,即 AQS)
|
| 具体实现
↓
公平锁(FairSync) 或 非公平锁(NonfairSync)
静态抽象内部类Sync
/**
* Base of synchronization control for this lock. Subclassed
* into fair and nonfair versions below. Uses AQS state to
* represent the number of holds on the lock.
*/
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
/**
* Performs {@link Lock#lock}. The main reason for subclassing
* is to allow fast path for nonfair version.
*/
abstract void lock();
/**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
*/
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); // 设置新的state,无需CAS(因为已持有锁)
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
int c = getState() - releases; // 计算释放后的state
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException(); // 安全校验
boolean free = false;
if (c == 0) { // 完全释放锁
free = true;
setExclusiveOwnerThread(null); // 清除锁持有者
}
setState(c); // 设置新state
return free; // 返回是否完全释放
}
// 判断当前线程是否持有锁
protected final boolean isHeldExclusively() {
// While we must in general read state before owner,
// we don't need to do so to check if current thread is owner
return getExclusiveOwnerThread() == Thread.currentThread();
}
// 创建与当前锁关联的条件变量
final ConditionObject newCondition() {
return new ConditionObject();
}
// Methods relayed from outer class
final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}
final int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
}
final boolean isLocked() {
return getState() != 0;
}
/**
* Reconstitutes the instance from a stream (that is, deserializes it).
*/
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
setState(0); // reset to unlocked state
}
}
静态内部类 NonfairSync
/**
* Sync object for non-fair locks
*/
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires); // 复用父类的非公平获取逻辑
}
}
关键设计:
- 两级尝试机制:先在
lock()中直接CAS,失败后再通过acquire()进行完整尝试 - 性能优化:避免了不必要的队列操作,新线程有机会"抢到"刚释放的锁
- 饥饿风险:新线程和队列中等待的线程竞争,可能导致等待线程长期获取不到锁
静态内部类 FairSync
/**
* Sync object for fair locks
*/
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
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;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
公平锁在获取锁时会先判断当前锁是否有其他等待线程,如果有的话需要进行排队。这里的关键函数是hasQueuedPredecessors ,它判断队列中是否有优先级更高的等待线程:
// AbstractQueuedSynchronizer.java
public final boolean hasQueuedPredecessors() {
// 读取尾节点和头节点
Node t = tail;
Node h = head;
Node s;
// 判断是否有前驱节点的三种情况:
return h != t && // 队列不为空(头尾不同)
((s = h.next) == null || // 情况1:队列正在初始化
s.thread != Thread.currentThread()); // 情况2:头节点的下一个节点不是当前线程
}
公平锁执行流程:
sequenceDiagram
participant Thread as 新线程
participant FairSync as 公平锁
participant AQS as AQS队列
participant Lock as 锁状态
Thread->>FairSync: lock()
FairSync->>AQS: acquire(1)
AQS->>FairSync: tryAcquire(1)
alt 锁空闲
FairSync->>AQS: hasQueuedPredecessors()
alt 没有前驱节点
FairSync->>Lock: compareAndSetState(0,1)
Lock-->>FairSync: 成功
FairSync->>FairSync: setExclusiveOwnerThread()
FairSync-->>AQS: return true
AQS-->>Thread: 获取成功
else 有前驱节点
FairSync-->>AQS: return false
AQS->>AQS: addWaiter()
AQS->>AQS: acquireQueued()
loop 自旋等待
AQS->>FairSync: tryAcquire(1)
FairSync->>AQS: return false
AQS->>AQS: parkAndCheckInterrupt()
end
end
else 锁被占用
FairSync->>FairSync: 检查是否重入
alt 是当前线程持有
FairSync->>Lock: state++
FairSync-->>AQS: return true
AQS-->>Thread: 重入成功
else 非当前线程持有
FairSync-->>AQS: return false
AQS->>AQS: 入队等待
end
end
特殊说明
关于公平锁对队列初始化情况的判断
前面介绍公平锁判断是否有前驱节点的时候,判断了队列是否初始化的情况:
// AbstractQueuedSynchronizer.java
public final boolean hasQueuedPredecessors() {
// 读取尾节点和头节点
Node t = tail;
Node h = head;
Node s;
// 判断是否有前驱节点的三种情况:
return h != t && // 队列不为空(头尾不同)
((s = h.next) == null || // 情况1:队列正在初始化
s.thread != Thread.currentThread()); // 情况2:头节点的下一个节点不是当前线程
}
即主要是这段代码:
(s = h.next) == null // 情况1:队列正在初始化
当 hasQueuedPredecessors() 遇到 h.next == null 时,它返回 true,意思是:有前驱节点(或者队列正在初始化,你应该等待)。这个设计是保守的:
- 宁可让新线程等待(返回true),也不让它错误地认为队列为空
- 避免在队列初始化过程中出现竞争问题
当线程进入等待队列的时候,会调用如下代码:
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // 阶段1:队列完全为空
// 创建空节点作为头节点
if (compareAndSetHead(new Node()))
tail = head; // 此时: head = 空节点, tail = 空节点
} else { // 阶段2:将新节点添加到队尾
node.prev = t;
if (compareAndSetTail(t, node)) { // 步骤A: 设置tail为新节点
t.next = node; // 步骤B: 设置prev节点的next指针
return t;
}
}
}
}
这里会发生并发冲突,上面对初始化判断的case主要会发生在如下场景:
graph TD
subgraph "enq() 执行过程"
A[开始入队] --> B{t == null?}
B -->|是| C[CAS设置head为新建的空节点]
C --> D[tail = head]
D --> E[循环继续]
B -->|否| F[node.prev = t]
F --> G[CAS设置tail = node]
G --> H[t.next = node]
H --> I[返回]
end
subgraph "hasQueuedPredecessors() 检查"
J[读取h = head] --> K[读取t = tail]
K --> L{h != t?}
L -->|是| M[读取s = h.next]
M --> N{s == null?}
N -->|是| O[返回true<br>队列初始化中]
N -->|否| P[检查s.thread]
end
G -->|此时状态:<br>tail已更新为node<br>但t.next尚未设置| Q[竞争窗口]
Q --> N
在这个竞争窗口中:
tail已经指向新节点(线程B的节点)- 但
head.next还没有被设置(t.next = node尚未执行)
时间窗口中的队列状态:
head tail
↓ ↓
[空节点] [节点B]
↑ ↑
head.next = null tail = node
此时 h != t(头尾不同)但 h.next == null,这表示队列正在初始化,新节点还没有完全链接到队列中。