Java 內置鎖 synchronized 及 ReentrantLock 默認模式均為非公平鎖:線程釋放鎖後,等待隊列中的線程與新請求鎖的線程競爭,可能導致線程飢餓——優先級低或遲到的線程長期無法獲取鎖。公平鎖嚴格按照請求順序 (FIFO) 授予鎖,消除飢餓,代價是吞吐量下降。
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
FairSync:繼承 Sync → AbstractQueuedSynchronizer (AQS),重寫 tryAcquire 增加 !hasQueuedPredecessors() 判斷。NonfairSync:直接嘗試 CAS 獲取鎖,不檢查隊列前驅。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()) {
// 重入邏輯
setState(c + acquires);
return true;
}
return false;
}
hasQueuedPredecessors() 檢查當前線程節點是否為隊列首節點(頭節點的下一個),若否則排隊等待,實現 FIFO。
public class FairLockDemo implements Runnable {
// true 開啟公平鎖
private static final ReentrantLock fairLock = new ReentrantLock(true);
// private static final ReentrantLock unfairLock = new ReentrantLock(false); // 對比用
@Override public void run() {
for (int i = 0; i < 5; i++) {
fairLock.lock();
try {
System.out.println(Thread.currentThread().getName() + " 獲得鎖 - 第 " + (i+1) + " 次");
// 模擬業務耗時
Thread.sleep(10);
} catch (InterruptedException e) { Thread.currentThread().interrupt(); }
finally { fairLock.unlock(); }
}
}
public static void main(String[] args) {
FairLockDemo demo = new FairLockDemo();
new Thread(demo, "Thread-A").start();
new Thread(demo, "Thread-B").start();
new Thread(demo, "Thread-C").start();
}
}
Thread-A 獲得鎖 - 第 1 次 Thread-B 獲得鎖 - 第 1 次 Thread-C 獲得鎖 - 第 1 次 Thread-A 獲得鎖 - 第 2 次 Thread-B 獲得鎖 - 第 2 次 Thread-C 獲得鎖 - 第 2 次 ...
三個線程嚴格輪轉,無連續搶佔。若改為非公平鎖,輸出將呈現明顯的「批次式」搶佔(某線程連續多次)。
| 維度 | 公平鎖 (FairSync) | 非公平鎖 (NonfairSync / synchronized) |
|---|---|---|
| 吞吐量 | 較低 (需維護隊列順序、頻繁上下文切換) | 高 (新線程可「插隊」獲取鎖,減少切換) |
| 線程飢餓 | 不可能 | 極端情況下可能 |
| 響應時間均勻性 | 極佳 (方差極小) | 波動大 |
| 適用場景 | 對公平性敏感、執行時間較長的關鍵區 (如寫入操作、計費扣款) | 高併發、短耗時、追求極致吞吐 (如緩存讀取、計數器) |
ReentrantLock fairLock = new ReentrantLock(true);
Condition notFull = fairLock.newCondition();
Condition notEmpty = fairLock.newCondition();
// 生產者
fairLock.lock();
try {
while (queue.size() == capacity) notFull.await();
queue.add(item);
notEmpty.signal();
} finally { fairLock.unlock(); }
// 消費者
fairLock.lock();
try {
while (queue.isEmpty()) notEmpty.await();
Item item = queue.poll();
notFull.signal();
return item;
} finally { fairLock.unlock(); }
公平鎖確保等待隊列中的線程按順序被喚醒,避免「驚群效應」下的飢餓。
new ReentrantLock() 或 synchronized,性能優勢明顯。new ReentrantLock(true)。fairLock.tryLock(5, TimeUnit.SECONDS) 防止死等。LockSupport.parkNanos 時間、線程阻塞佇列長度,及時發現鎖爭用熱點。ReentrantLock 公平鎖基於 AQS 隊列實現 FIFO 授權,解決線程飢餓問題,但引入額外同步開銷導致吞吐量下降 20%-50%。工程上默認非公平,按需開啟公平,並結合 tryLock、Condition 構建高可用併發組件。