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

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

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

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

通過(guò)合理選擇鎖策略,可以在高并發(fā)場(chǎng)景下平衡性能與公平性!
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
spring boot + jpa + kotlin入門(mén)實(shí)例詳解
這篇文章主要介紹了spring boot + jpa + kotlin入門(mén)實(shí)例詳解 ,需要的朋友可以參考下2017-07-07
Java中實(shí)現(xiàn)OCR識(shí)別讀取圖片中的文字
圖片內(nèi)容一般無(wú)法編輯,如果想要讀取圖片中的文本,我們需要用到OCR工具,本文將介紹如何在Java中實(shí)現(xiàn)OCR識(shí)別讀取圖片中的文字,文中通過(guò)代碼示例介紹的非常詳細(xì),需要的朋友可以參考下2024-04-04
Java多線(xiàn)程模擬售票程序和線(xiàn)程安全問(wèn)題
本篇文章給大家分享了Java多線(xiàn)程模擬售票程序和線(xiàn)程安全問(wèn)題的相關(guān)知識(shí)點(diǎn),對(duì)此有興趣的朋友可以參考學(xué)習(xí)下。2018-05-05
Java中@DS+@Transactional注解切換數(shù)據(jù)源失效解決方案
本文主要介紹了@DS+@Transactional注解切換數(shù)據(jù)源失效解決方案,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-06-06
java實(shí)現(xiàn)簡(jiǎn)單的猜數(shù)字小游戲
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)簡(jiǎn)單猜數(shù)字小游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-03-03
Java8日期時(shí)間類(lèi)LocalDateTime比較大小舉例
LocalDate是Java?8中的日期類(lèi)之一,它表示一個(gè)日期,下面這篇文章主要給大家介紹了關(guān)于Java8日期時(shí)間類(lèi)LocalDateTime比較大小的相關(guān)資料,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-05-05

