關(guān)于ReadWriteLock讀寫鎖的使用及說明
ReentrantReadWriteLock實現(xiàn)了ReadWriteLock接口。位于java.util.concurrent.locks;

1、普通鎖
讀寫互斥,如ReentrantLock。
1.1、原理
- 普通鎖是排他鎖(Exclusive Lock):無論讀還是寫,同一時刻只能有一個線程持有鎖。
- 所有操作互斥:即使多個線程只是讀取數(shù)據(jù),普通鎖也會阻塞其他線程。
代碼示例:
ReentrantLock lock = new ReentrantLock();
void read() {
lock.lock();
try {
// 讀取數(shù)據(jù)
} finally {
lock.unlock();
}
}
void write() {
lock.lock();
try {
// 寫入數(shù)據(jù)
} finally {
lock.unlock();
}
}
1.2、特點
- 讀線程會阻塞其他讀線程:即使沒有寫操作,讀線程之間也不能并發(fā)。
- 性能低:在高并發(fā)讀場景下,資源利用率低。
2、ReadWriteLock
讀寫分離機制。
- 基于 AQS:通過
state字段的高位和低位分別管理讀鎖和寫鎖。 - 共享鎖(Shared):允許多個線程同時讀。
- 排他鎖(Exclusive):寫操作獨占鎖。
2.1、核心思想
規(guī)則:讀鎖與讀鎖不互斥。讀鎖與寫鎖互斥。寫鎖與寫鎖互斥。
讀鎖(共享鎖):
- 多個線程可同時持有讀鎖。
- 獲取讀鎖時,需確保沒有寫鎖存在。
- 讀鎖可重入(同一線程多次獲取讀鎖時,
state高位增加)。
寫鎖(排他鎖):
- 寫鎖獨占,阻塞所有讀和寫操作。
- 寫鎖可重入(同一線程多次獲取寫鎖時,
state低位增加)。 - 寫鎖可降級為讀鎖(但不能升級為寫鎖)。
鎖升級/降級規(guī)則:
- 不允許升級:讀鎖不能直接升級為寫鎖(會破壞公平性,可能導致死鎖)。
- 允許降級:寫鎖可以降級為讀鎖(需顯式釋放寫鎖后獲取讀鎖)。
代碼示例:
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
Lock readLock = readWriteLock.readLock();
Lock writeLock = readWriteLock.writeLock();
void read() {
readLock.lock();
try {
// 讀取數(shù)據(jù)(多個線程可同時讀)
} finally {
readLock.unlock();
}
}
void write() {
writeLock.lock();
try {
// 寫入數(shù)據(jù)(獨占)
} finally {
writeLock.unlock();
}
}
為什么讀鎖和寫鎖可以“部分共存”?
- 讀鎖不阻塞其他讀鎖:因為讀操作不會修改數(shù)據(jù),多個線程讀取共享數(shù)據(jù)是安全的。
- 寫鎖阻塞所有讀寫:寫操作需要獨占數(shù)據(jù),防止臟讀和數(shù)據(jù)不一致。
2.2、特點
1、高效
適合高并發(fā)讀的場景。
- 普通鎖:多個讀線程互相阻塞,吞吐量低。
- 讀寫鎖:多個讀線程可并發(fā)讀取,吞吐量高。
2、緩存讀取和更新
class Cache {
private Object data;
private ReadWriteLock lock = new ReentrantReadWriteLock();
void get() {
lock.readLock().lock();
try {
// 多個線程可同時讀取
return data;
} finally {
lock.readLock().unlock();
}
}
void put(Object newData) {
lock.writeLock().lock();
try {
// 寫入時獨占
data = newData;
} finally {
lock.writeLock().unlock();
}
}
}
- 優(yōu)勢:緩存讀取頻繁,寫入較少,使用讀寫鎖可大幅提升并發(fā)性能。
2.3、鎖共存
寫鎖不能與讀鎖或?qū)戞i共存。具體是為什么,可參考以下數(shù)據(jù)一致性和state字段來進行分析。
1. 數(shù)據(jù)一致性要求
寫操作必須獨占:如果允許寫鎖與讀鎖或?qū)戞i共存,可能導致:
- 臟讀:讀線程讀到未提交的數(shù)據(jù)。
- 數(shù)據(jù)不一致:多個寫線程同時修改數(shù)據(jù),導致結(jié)果不可預測。
2. 內(nèi)部實現(xiàn)限制
讀寫鎖的實現(xiàn):
- 使用一個
int類型的state字段,高16位表示讀鎖數(shù)量,低16位表示寫鎖重入次數(shù)。 - 寫鎖獲取時:必須確保當前沒有讀鎖或?qū)戞i。
- 讀鎖獲取時:必須確保當前沒有寫鎖。
2.4、關(guān)鍵字段
state:高位(32位)表示讀鎖數(shù)量,低位(32位)表示寫鎖重入次數(shù)。readLock和writeLock:分別管理讀鎖和寫鎖的獲取與釋放。
以下是常用的方法:
readLock().lock():嘗試獲取共享鎖。writeLock().lock():嘗試獲取排他鎖。readLock().unlock()和writeLock().unlock():釋放對應鎖。
2.5、獲取流程
1、寫鎖
- 檢查當前是否有寫鎖(通過
exclusiveCount判斷)。 - 檢查是否有讀鎖(通過
sharedCount判斷)。 - 如果沒有讀鎖和寫鎖,則設(shè)置寫鎖狀態(tài)。
- 否則,將線程加入等待隊列。
2、讀鎖
- 檢查當前是否有寫鎖。
- 如果沒有寫鎖,則嘗試增加讀鎖計數(shù)。
- 如果有寫鎖或讀鎖溢出,則將線程加入等待隊列。
小結(jié)
如何選擇哪種鎖,可根據(jù)以下場景進行分析:
選擇普通鎖:
- 數(shù)據(jù)操作簡單(如單次寫入后只讀)。
- 不需要區(qū)分讀寫操作。
選擇讀寫鎖:
- 讀操作遠多于寫操作(如緩存、配置中心)。
- 需要提升讀并發(fā)性能。
對比
普通鎖 vsReadWriteLock:

3、寫鎖饑餓
3.1、原因
1. 優(yōu)先級
- ReentrantReadWriteLock 默認是非公平模式(
fair=false)。 - 讀鎖的優(yōu)先級更高:在非公平模式下,讀鎖可以“插隊”獲取鎖,即使有等待的寫線程。
- 寫鎖需要獨占鎖:寫操作必須阻塞所有讀和寫,因此寫線程會一直等待,直到所有讀線程釋放讀鎖。
2. 等待隊列機制
AQS(AbstractQueuedSynchronizer)維護一個 FIFO 隊列。
非公平模式下:
- 讀線程可以“插隊”獲取鎖(無需排隊)。
- 寫線程只能按順序等待,直到?jīng)]有讀線程。
示例:
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
// 線程 A: 讀線程
lock.readLock().lock();
try {
while (true) {
// 持續(xù)讀取(不釋放讀鎖)
}
} finally {
lock.readLock().unlock();
}
// 線程 B: 寫線程
lock.writeLock().lock(); // 被阻塞,永遠無法獲取寫鎖
3.2、實現(xiàn)原理
1. 寫鎖獲取流程
檢查當前是否有寫鎖(通過exclusiveCount判斷)。
檢查是否有讀鎖(通過sharedCount判斷)。
非公平模式下:
- 如果沒有寫鎖,且當前線程可以插隊(無需等待),則直接獲取寫鎖。
- 如果有讀鎖或?qū)戞i,則將線程加入等待隊列。
公平模式下:
- 寫線程必須按順序等待,即使沒有讀鎖。
2. 寫鎖釋放流程
- 釋放寫鎖后,喚醒等待隊列中的線程。
非公平模式下:
- 新來的讀線程可能再次插隊獲取讀鎖。
- 寫線程仍需等待所有讀線程釋放讀鎖。
3.3、避免寫鎖饑餓
1. 使用公平模式(Fair Mode)
- 配置公平鎖:
new ReentrantReadWriteLock(true)。
效果:
- 寫線程按順序獲取鎖,不會被讀線程插隊。
- 優(yōu)點:避免寫鎖饑餓。
- 缺點:性能略低(讀線程無法插隊)。
代碼示例:
ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true); // 公平模式
void read() {
lock.readLock().lock();
try {
// 讀取數(shù)據(jù)
} finally {
lock.readLock().unlock();
}
}
void write() {
lock.writeLock().lock();
try {
// 寫入數(shù)據(jù)
} finally {
lock.writeLock().unlock();
}
}
公平模式下和非公平模式下:

2.限制讀鎖的持有時間
避免讀線程長期占用讀鎖:
- 在業(yè)務邏輯中控制讀鎖的持有時間。
- 避免在讀鎖內(nèi)執(zhí)行長時間操作。
3. 使用StampedLock
在Java 8+,StampedLock提供更靈活的讀寫鎖策略:
- 支持樂觀讀鎖(不阻塞寫鎖)。
- 支持寫鎖優(yōu)先級(避免讀鎖插隊)。
代碼示例:
StampedLock lock = new StampedLock();
void read() {
long stamp = lock.tryOptimisticRead();
if (lock.validate(stamp)) {
// 樂觀讀取(不阻塞寫鎖)
}
}
void write() {
long stamp = lock.writeLock();
try {
// 寫入數(shù)據(jù)
} finally {
lock.unlockWrite(stamp);
}
}
總結(jié)

通過合理選擇鎖策略,可以在高并發(fā)場景下平衡性能與公平性!
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
spring boot + jpa + kotlin入門實例詳解
這篇文章主要介紹了spring boot + jpa + kotlin入門實例詳解 ,需要的朋友可以參考下2017-07-07
Java中@DS+@Transactional注解切換數(shù)據(jù)源失效解決方案
本文主要介紹了@DS+@Transactional注解切換數(shù)據(jù)源失效解決方案,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-06-06

