Java中的ReadWriteLock讀寫鎖實(shí)例詳解
在之前的文章中,我們已經(jīng)學(xué)習(xí)了synchronized和ReentrantLock。通過(guò)學(xué)習(xí)我們知道這兩者都是屬于排它鎖,也叫做互斥鎖。
什么叫互斥鎖呢?就是不管哪個(gè)線程去讀取數(shù)據(jù)還是修改數(shù)據(jù),只要他持有鎖,那其他的線程就必須等著。
這在大多數(shù)情況下肯定是線程安全的,沒(méi)什么問(wèn)題。
但是在實(shí)際的開(kāi)發(fā)過(guò)程中,有一些很常見(jiàn)的讀多寫少的場(chǎng)景。像配置信息,熱點(diǎn)數(shù)據(jù)的緩存。這些數(shù)據(jù)實(shí)際上被大量的線程進(jìn)行頻繁的讀取,而只在極少的情況下會(huì)去修改。
在這類場(chǎng)景下,如果讀取操作也使用排他鎖,想想都會(huì)降低系統(tǒng)性能。因?yàn)樽x取操作本身并不會(huì)改變數(shù)據(jù),多個(gè)線程同時(shí)讀取同一個(gè)數(shù)據(jù)是完全安全的。
那有沒(méi)有一種鎖能把讀取和寫入?yún)^(qū)別開(kāi)呢?
那肯定是有的,JUC包下的ReadWriteLock就是為解決這個(gè)問(wèn)題而生的。
今天我們就一起來(lái)看看ReadWriteLock。
一、什么是ReadWriteLock
ReadWriteLock是java.util.concurrent.locks中的一個(gè)接口。既然是接口,那他就不是一把具體的鎖。
可以把他理解成一個(gè)鎖的管理者。他內(nèi)部管理了兩把相互關(guān)聯(lián)的鎖:
一個(gè)讀取鎖,也叫做共享鎖。
一個(gè)寫入所,也叫排它鎖。
再提一句,源碼中的注釋真的有必要去看一看,不管是學(xué)習(xí)前還是學(xué)習(xí)后,盡量都去閱讀下源碼及注釋。

ReentrantReadWriteLock是ReadWriteLock接口最常用的實(shí)現(xiàn)類。
這套鎖機(jī)制有以下的核心原則:
讀-讀 共享:多個(gè)線程可以同時(shí)持有讀取鎖,進(jìn)行并發(fā)讀取。
讀-寫 互斥:當(dāng)有線程持有讀取鎖時(shí),寫入線程必須等待。
寫-讀 互斥:當(dāng)有線程持有寫入鎖時(shí),所有其他線程(不管讀寫)都必須等待。
寫-寫 互斥:當(dāng)有線程持有寫入鎖時(shí),其他寫入線程也必須等待。
其實(shí)很好理解,讀鎖大家用,寫鎖只能一個(gè)人用。
大家都是讀,其樂(lè)融融,隨便讀。
我在讀,你想寫,你得等著,等我讀完了,不然就存在邊讀邊寫的詭異事情。
我在寫,你想讀或者你也想寫,那都得等著,不然又會(huì)混亂了。
簡(jiǎn)單的總結(jié)就是:讀鎖大家用,寫鎖我獨(dú)占。
二、讀寫鎖使用
我們通過(guò)一個(gè)小案例來(lái)看一下讀寫鎖的使用:
package com.lazy.snail.day40;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* @ClassName Day40Demo
* @Description TODO
* @Author lazysnail
* @Date 2025/8/12 9:52
* @Version 1.0
*/
public class Day40Demo {
private final Map<String, String> map = new HashMap<>();
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Lock rLock = rwLock.readLock();
private final Lock wLock = rwLock.writeLock();
public String get(String key) {
rLock.lock();
try { return map.get(key); }
finally { rLock.unlock(); }
}
public void put(String key, String value) {
wLock.lock();
try { map.put(key, value); }
finally { wLock.unlock(); }
}
}之前的集合框架學(xué)習(xí)中,我們已經(jīng)知道HashMap自身不是線程安全的,但是我們可以用鎖把所有的訪問(wèn)都包起來(lái)。來(lái)保證線程安全。
rwLock是我們創(chuàng)建的讀寫鎖管理器,生成兩把關(guān)聯(lián)的鎖:讀鎖、寫鎖。
rLock和wLock分別取出讀鎖和寫鎖。
get方法代表只讀路徑,也就是讀取。在沒(méi)有線程持有寫鎖/等待寫的時(shí)候,多個(gè)線程可以同時(shí)獲取讀鎖。
在持鎖期間進(jìn)行讀?。╩ap.get()),避免了跟寫線程并發(fā)導(dǎo)致的數(shù)據(jù)競(jìng)爭(zhēng)。
最后在finally里面釋放鎖。
put方法代表寫路徑,也就是寫入。
寫鎖是獨(dú)占的。只要有線程持有寫鎖,其他讀/寫都得等;反過(guò)來(lái),只要有讀鎖在,寫鎖也進(jìn)不來(lái)。
唯一寫入口(map.put),確保不會(huì)出現(xiàn)同時(shí)寫或讀寫交疊。
同樣在finally中釋放鎖。
三、內(nèi)部原理
3.1 鎖實(shí)現(xiàn)及獲取
ReentrantReadWriteLock內(nèi)部并不是維護(hù)兩把完全獨(dú)立的鎖,而是用一個(gè)32位int變量state同時(shí)記錄讀寫狀態(tài):
其中高16位表示讀鎖計(jì)數(shù),允許多個(gè)線程同時(shí)增加。
低16位表示寫鎖計(jì)數(shù),同一個(gè)線程可以重入。
ReentrantReadWriteLock中相關(guān)的核心常量:

獲取寫鎖:
1.檢查state是不是為0。如果不是0,意味著要么有讀鎖存在 (readCount > 0),要么已經(jīng)有其他線程持有了寫鎖 (writeCount > 0)。
2.如果state不是0,但持有寫鎖的是當(dāng)前線程,就可以重入,把writeCount加1。
3.如果state是0,那么當(dāng)前線程可以獲取寫鎖,把writeCount加1,把當(dāng)前線程設(shè)置成鎖的持有者。
獲取讀鎖:
1.檢查是不是存在寫鎖 (writeCount > 0),且持有寫鎖的不是當(dāng)前線程。如果是,則獲取失敗,進(jìn)入等待隊(duì)列。
2.如果沒(méi)有寫鎖,或者寫鎖被當(dāng)前線程持有,那么當(dāng)前線程可以獲取讀鎖,通過(guò)CAS操作把readCount加1。
3.2 公平策略
ReentrantReadWriteLock跟ReentrantLock一樣,也支持公平和非公平兩種模式。
非公平鎖:new ReentrantReadWriteLock()和new ReentrantReadWriteLock(false)都是非公平鎖構(gòu)造。允許新來(lái)的線程插隊(duì)。舉個(gè)例子,當(dāng)寫鎖被釋放的時(shí)候,如果等待隊(duì)列里有讀線程和寫線程,同時(shí)又有一個(gè)新的讀線程請(qǐng)求鎖,那么這個(gè)新的讀線程可能優(yōu)先獲得鎖。從例子就能看出優(yōu)點(diǎn)肯定是吞吐量更高,但可能會(huì)導(dǎo)致寫線程饑餓。
公平鎖:new ReentrantReadWriteLock(true)。這種模式就嚴(yán)格按照線程在等待隊(duì)列里的FIFO順序來(lái)分配鎖。如果隊(duì)列頭部是寫線程在等待,那所有后來(lái)的讀鎖請(qǐng)求都必須排隊(duì)。優(yōu)點(diǎn)就是能防止饑餓,保證公平性,一般情況下性能會(huì)低于非公平鎖,畢竟要維持公平性。
3.3 鎖升降級(jí)
鎖降級(jí)指的是線程在持有寫鎖的情況下,繼續(xù)獲取讀鎖,然后釋放寫鎖。
鎖升級(jí)指的是線程在持有讀鎖的情況下,去獲取寫鎖。ReentrantReadWriteLock是不支持的。
鎖降級(jí)
考慮這樣一個(gè)場(chǎng)景,如果有一個(gè)線程更新了共享數(shù)據(jù)后,還需要讀取這些數(shù)據(jù)處理其他業(yè)務(wù),但這個(gè)時(shí)候已經(jīng)不希望再獨(dú)占資源,而是允許其他讀線程進(jìn)來(lái)。
wLock.lock(); // 獲取寫鎖
try {
// 修改數(shù)據(jù)...
map.put("name", "懶惰蝸牛");
// 鎖降級(jí):在持有寫鎖的情況下獲取讀鎖
rLock.lock();
// 釋放寫鎖(這個(gè)時(shí)候仍然持有讀鎖)
// 其他線程現(xiàn)在可以獲取讀鎖了
wLock.unlock();
// 使用讀鎖狀態(tài)讀取數(shù)據(jù),執(zhí)行后續(xù)操作...
// 保證了在讀取期間,數(shù)據(jù)不會(huì)被其他寫線程修改
System.out.println("讀取數(shù)據(jù): " + map.get("name"));
} finally {
// 釋放讀鎖
rLock.unlock();
}鎖升級(jí)
為什么不支持鎖升級(jí),如果兩個(gè)線程(T1, T2)同時(shí)持有讀鎖,然后他們都嘗試升級(jí)成寫鎖。T1要等待T2釋放讀鎖,而T2也要等待T1釋放讀鎖,就這樣相互等著,最后就形成死鎖了。
所以必須先rLock.unlock(),然后再wLock.lock()。但還是要注意的是,在釋放讀鎖和獲取寫鎖的間隙,數(shù)據(jù)可能被其他線程修改,原子性被破壞了,這就需要我們自己的業(yè)務(wù)邏輯來(lái)處理。
四、小結(jié)
目前我們已經(jīng)接觸了synchronized、ReentrantLock和ReentrantReadWriteLock。
下面對(duì)這三種形式的鎖用表格進(jìn)行清晰的對(duì)比:
| 特性 | synchronized | ReentrantLock | ReentrantReadWriteLock |
|---|---|---|---|
| 鎖類型 | 互斥鎖 | 互斥鎖 | 讀寫分離鎖(共享/互斥) |
| 底層實(shí)現(xiàn) | JVM指令 | AQS | AQS |
| 公平性 | 非公平 | 可選(默認(rèn)非公平) | 可選(默認(rèn)非公平) |
| 可中斷 | 不可中斷 | 可中斷 | 可中斷 |
| 嘗試獲取鎖 | 不支持 | 支持 (tryLock) | 支持 (tryLock) |
| Condition | wait/notify | Condition | 僅寫鎖支持Condition |
| 適用場(chǎng)景 | 簡(jiǎn)單同步代碼塊 | 需高級(jí)功能的復(fù)雜同步 | 讀多寫少的場(chǎng)景 |
每種形式的鎖都有各自的優(yōu)缺點(diǎn),都有自己的應(yīng)用場(chǎng)景。
到此這篇關(guān)于Java中的ReadWriteLock讀寫鎖的文章就介紹到這了,更多相關(guān)Java ReadWriteLock讀寫鎖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Java并發(fā)讀寫鎖ReentrantReadWriteLock 使用場(chǎng)景
- java實(shí)現(xiàn)ReadWriteLock讀寫鎖的示例
- Java中的ReadWriteLock讀寫鎖詳解
- Java讀寫鎖ReadWriteLock原理與應(yīng)用場(chǎng)景詳解
- Java讀寫鎖ReadWriteLock的創(chuàng)建使用及測(cè)試分析示例詳解
- Java中讀寫鎖ReadWriteLock的原理與應(yīng)用詳解
- Java多線程編程之讀寫鎖ReadWriteLock用法實(shí)例
- Java并發(fā)編程之顯示鎖ReentrantLock和ReadWriteLock讀寫鎖
相關(guān)文章
Spring中@ControllerAdvice注解的用法解析
這篇文章主要介紹了Spring中@ControllerAdvice注解的用法解析,顧名思義,@ControllerAdvice就是@Controller 的增強(qiáng)版,@ControllerAdvice主要用來(lái)處理全局?jǐn)?shù)據(jù),一般搭配@ExceptionHandler、@ModelAttribute以及@InitBinder使用,需要的朋友可以參考下2023-10-10
Java?awt-對(duì)話框簡(jiǎn)單實(shí)現(xiàn)方式
這篇文章主要介紹了Java?awt-對(duì)話框簡(jiǎn)單實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12
Java使用application.property讀取文件里面的值
本文通過(guò)實(shí)例代碼給大家介紹了Java使用application.property讀取文件里面的值,需要的朋友可以參考下2018-10-10
Spring?注入靜態(tài)對(duì)象使用三種方式示例
這篇文章主要為大家介紹了Spring注入靜態(tài)對(duì)象使用的三種方式示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07
ElasticSearch學(xué)習(xí)之文檔API相關(guān)操作
這篇文章主要為大家介紹了ElasticSearch學(xué)習(xí)之文檔API相關(guān)操作,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01
Springboot項(xiàng)目基于Devtools實(shí)現(xiàn)熱部署步驟詳解
這篇文章主要介紹了Springboot項(xiàng)目基于Devtools實(shí)現(xiàn)熱部署,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-06-06
純Java環(huán)境下實(shí)現(xiàn)將在線地圖路網(wǎng)數(shù)據(jù)轉(zhuǎn)為GeoJSON與WKT
這篇文章主要為大家詳細(xì)介紹了如何純Java環(huán)境下實(shí)現(xiàn)將在線地圖路網(wǎng)數(shù)據(jù)轉(zhuǎn)為GeoJSON與WKT,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解下2026-02-02

