JDK 5+ 在 java.util.concurrent.locks 提供 ReentrantReadWriteLock,但理解其底層原理對掌握併發編程至關重要。本文從零手寫一個功能完整的讀寫鎖,涵蓋基本互斥、重入、讀升寫、寫降讀、條件變量防信號丟失/虛假喚醒。
public class ReadWriteLock {
private int readers = 0, writers = 0, writeRequests = 0;
public synchronized void lockRead() throws InterruptedException {
while (writers > 0 || writeRequests > 0) wait();
readers++;
}
public synchronized void unlockRead() {
readers--;
notifyAll();
}
public synchronized void lockWrite() throws InterruptedException {
writeRequests++;
while (readers > 0 || writers > 0) wait();
writeRequests--;
writers++;
}
public synchronized void unlockWrite() {
writers--;
notifyAll();
}
}
關鍵點:notifyAll() 而非 notify(),避免喚醒的讀線程因寫請求存在而再次等待,導致寫線程永不被喚醒(信號丟失)。
Map<Thread, Integer> 記錄每線程持有讀鎖計數private Map<Thread, Integer> readingThreads = new HashMap<>();
private boolean canGrantReadAccess(Thread t) {
if (writers > 0) return false;
if (isReader(t)) return true; // 重入
if (writeRequests > 0) return false; // 寫優先
return true;
}
writingThread 及計數 writeAccessesprivate Thread writingThread = null;
private int writeAccesses = 0;
private boolean canGrantWriteAccess(Thread t) {
if (hasReaders()) return false;
if (writingThread == null) return true;
return isWriter(t); // 重入
}
允許唯一讀線程升級為寫鎖:
private boolean isOnlyReader(Thread t) {
return readingThreads.size() == 1 && readingThreads.containsKey(t);
}
private boolean canGrantWriteAccess(Thread t) {
if (isOnlyReader(t)) return true; // 升級
if (hasReaders()) return false;
// ... 原有邏輯
}
持有寫鎖的線程直接獲取讀鎖(無其他線程持有鎖):
private boolean canGrantReadAccess(Thread t) {
if (isWriter(t)) return true; // 降級
if (writingThread != null) return false;
// ... 原有邏輯
}
public class ReadWriteLock {
private final Map<Thread, Integer> readingThreads = new HashMap<>();
private int writeAccesses = 0, writeRequests = 0;
private Thread writingThread = null;
public synchronized void lockRead() throws InterruptedException {
Thread t = Thread.currentThread();
while (!canGrantReadAccess(t)) wait();
readingThreads.put(t, getReadAccessCount(t) + 1);
}
public synchronized void unlockRead() {
Thread t = Thread.currentThread();
if (!isReader(t)) throw new IllegalMonitorStateException();
int c = getReadAccessCount(t);
if (c == 1) readingThreads.remove(t);
else readingThreads.put(t, c - 1);
notifyAll();
}
public synchronized void lockWrite() throws InterruptedException {
writeRequests++;
Thread t = Thread.currentThread();
while (!canGrantWriteAccess(t)) wait();
writeRequests--;
writeAccesses++;
writingThread = t;
}
public synchronized void unlockWrite() {
if (!isWriter(Thread.currentThread()))
throw new IllegalMonitorStateException();
if (--writeAccesses == 0) writingThread = null;
notifyAll();
}
// 輔助方法:canGrantReadAccess, canGrantWriteAccess, isReader, isWriter,
// hasReaders, isOnlyReader, getReadAccessCount, hasWriteRequests
}
臨界區可能拋出異常,必須在 finally 塊釋放鎖:
lock.lockWrite();
try {
// 臨界區代碼
} finally {
lock.unlockWrite();
}
否則異常導致鎖永不釋放,造成死鎖。可重入特性僅在同一線程後續獲取鎖時自動解鎖,不可依賴。
notifyAll() + while 循環檢查條件,防信號丟失與虛假喚醒ReentrantReadWriteLock,但原理必須精通