| 操作 | 獲取條件 | 並發性 |
|---|---|---|
| 讀鎖(共享鎖) | 無線程持有寫鎖,且無線程等待寫鎖 | 多線程可同時持有 |
| 寫鎖(排他鎖) | 無線程持有讀鎖或寫鎖 | 獨佔,阻塞其他讀/寫 |
寫優先策略:為防止寫線程飢餓,有寫請求時後續讀請求需等待。
public class SimpleReadWriteLock {
private int readingReaders = 0; // 正在讀的線程數
private int waitingWriters = 0; // 等待寫的線程數
private int writingWriter = 0; // 正在寫的線程數 (0/1)
public synchronized void lockRead() throws InterruptedException {
while (writingWriter > 0 || waitingWriters > 0) {
wait(); // 有寫在執行或等待,讀等待
}
readingReaders++;
}
public synchronized void unlockRead() {
readingReaders--;
notifyAll(); // 喚醒等待的寫線程
}
public synchronized void lockWrite() throws InterruptedException {
waitingWriters++;
while (readingReaders > 0 || writingWriter > 0) {
wait(); // 有讀或寫在執行,寫等待
}
waitingWriters--;
writingWriter = 1;
}
public synchronized void unlockWrite() {
writingWriter = 0;
notifyAll(); // 喚醒所有等待讀/寫線程
}
}
關鍵點:notifyAll() 而非 notify(),避免信號丟失(喚醒的線程可能不符合條件需重新等待)。
public class ReentrantReadWriteLock {
// 讀鎖:Thread -> 重入計數
private final Map readingThreads = new HashMap<>();
// 寫鎖:持有線程 + 重入計數
private Thread writingThread = null;
private int writeCount = 0;
private int waitingWriters = 0;
public synchronized void lockRead() throws InterruptedException {
Thread t = Thread.currentThread();
while (!canGrantRead(t)) wait();
readingThreads.put(t, readingThreads.getOrDefault(t, 0) + 1);
}
private boolean canGrantRead(Thread t) {
if (writingThread != null && writingThread != t) return false; // 他人持有寫鎖
if (readingThreads.containsKey(t)) return true; // 自己已持有讀鎖 -> 重入
return waitingWriters == 0; // 無寫等待 -> 允許新讀
}
public synchronized void unlockRead() {
Thread t = Thread.currentThread();
int count = readingThreads.getOrDefault(t, 0);
if (count == 0) throw new IllegalMonitorStateException("無讀鎖");
if (count == 1) readingThreads.remove(t);
else readingThreads.put(t, count - 1);
notifyAll();
}
public synchronized void lockWrite() throws InterruptedException {
Thread t = Thread.currentThread();
waitingWriters++;
while (!canGrantWrite(t)) wait();
waitingWriters--;
writingThread = t;
writeCount++;
}
private boolean canGrantWrite(Thread t) {
if (!readingThreads.isEmpty() && !readingThreads.containsKey(t)) return false; // 他人持有讀鎖
if (writingThread == null) return true; // 無寫鎖
return writingThread == t; // 自己持有寫鎖 -> 重入
}
public synchronized void unlockWrite() {
Thread t = Thread.currentThread();
if (writingThread != t) throw new IllegalMonitorStateException("無寫鎖");
if (--writeCount == 0) writingThread = null;
notifyAll();
}
}
基於 AQS (AbstractQueuedSynchronizer) 同步器:
ReadLock:共享模式獲取,tryAcquireSharedWriteLock:獨佔模式獲取,tryAcquireCondition(僅寫鎖支持)
public class ReentrantReadWriteLock implements ReadWriteLock, Serializable {
public ReentrantReadWriteLock() { this(false); }
public ReentrantReadWriteLock(boolean fair) { ... }
public ReentrantReadWriteLock.ReadLock readLock() { ... }
public ReentrantReadWriteLock.WriteLock writeLock() { ... }
public static class ReadLock implements Lock, Serializable { ... }
public static class WriteLock implements Lock, Serializable { ... }
}
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Lock readLock = rwLock.readLock();
private final Lock writeLock = rwLock.writeLock();
private final Map cache = new HashMap<>();
// 讀操作
public String get(String key) {
readLock.lock();
try {
return cache.get(key);
} finally {
readLock.unlock(); // 必須在 finally 中釋放
}
}
// 寫操作
public void put(String key, String value) {
writeLock.lock();
try {
cache.put(key, value);
} finally {
writeLock.unlock();
}
}
// 寫降讀:持有寫鎖期間讀取(避免釋放寫鎖後被其他寫搶佔)
public String putIfAbsentAndGet(String key, String value) {
writeLock.lock();
try {
String old = cache.get(key);
if (old == null) cache.put(key, value);
// 寫降讀:獲取讀鎖(不會阻塞)
readLock.lock();
try {
return cache.get(key);
} finally {
readLock.unlock();
}
} finally {
writeLock.unlock();
}
}
| 場景 | 推薦鎖 | 理由 |
|---|---|---|
| 讀多寫少、讀操作耗時長 | ReentrantReadWriteLock | 讀並發無阻塞,吞吐遠超互斥鎖 |
| 讀寫均衡、臨界區極短 | ReentrantLock / synchronized | RWLock 開銷大,反而較慢 |
| 高併發緩存、配置熱加載 | ReadWriteLock / StampedLock | StampedLock 樂觀讀更快 |
| 需要 Condition 等待條件 | ReentrantLock / RWLock.writeLock() | ReadLock 不支持 Condition |
validate(stamp) 驗證無寫發生則成功,否則升級悲觀讀ReentrantReadWriteLock
private final StampedLock sl = new StampedLock();
// 樂觀讀
public String get(String key) {
long stamp = sl.tryOptimisticRead();
String val = cache.get(key);
if (!sl.validate(stamp)) { // 有寫發生
stamp = sl.readLock(); // 升級悲觀讀
try { return cache.get(key); }
finally { sl.unlockRead(stamp); }
}
return val;
}
final 修飾,防止引用變更Condition,需等待條件用寫鎖或單獨 ReentrantLockStampedLock 或 ConcurrentHashMap 替代讀寫鎖是「共享-排他」並發控制的經典模式。理解其狀態機、重入規則、升降級邏輯與 AQS 實現原理,配合 StampedLock 樂觀讀優化,能精準解決高讀併發場景的性能瓶頸。工程實踐中,優先考慮無鎖數據結構(ConcurrentHashMap/CopyOnWriteArrayList),鎖是最後手段。