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

ReentrantReadWriteLock是ReadWriteLock接口最常用的實現類。
這套鎖機制有以下的核心原則:
讀-讀 共享:多個線程可以同時持有讀取鎖,進行并發(fā)讀取。
讀-寫 互斥:當有線程持有讀取鎖時,寫入線程必須等待。
寫-讀 互斥:當有線程持有寫入鎖時,所有其他線程(不管讀寫)都必須等待。
寫-寫 互斥:當有線程持有寫入鎖時,其他寫入線程也必須等待。
其實很好理解,讀鎖大家用,寫鎖只能一個人用。
大家都是讀,其樂融融,隨便讀。
我在讀,你想寫,你得等著,等我讀完了,不然就存在邊讀邊寫的詭異事情。
我在寫,你想讀或者你也想寫,那都得等著,不然又會混亂了。
簡單的總結就是:讀鎖大家用,寫鎖我獨占。
二、讀寫鎖使用
我們通過一個小案例來看一下讀寫鎖的使用:
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(); }
}
}之前的集合框架學習中,我們已經知道HashMap自身不是線程安全的,但是我們可以用鎖把所有的訪問都包起來。來保證線程安全。
rwLock是我們創(chuàng)建的讀寫鎖管理器,生成兩把關聯的鎖:讀鎖、寫鎖。
rLock和wLock分別取出讀鎖和寫鎖。
get方法代表只讀路徑,也就是讀取。在沒有線程持有寫鎖/等待寫的時候,多個線程可以同時獲取讀鎖。
在持鎖期間進行讀?。╩ap.get()),避免了跟寫線程并發(fā)導致的數據競爭。
最后在finally里面釋放鎖。
put方法代表寫路徑,也就是寫入。
寫鎖是獨占的。只要有線程持有寫鎖,其他讀/寫都得等;反過來,只要有讀鎖在,寫鎖也進不來。
唯一寫入口(map.put),確保不會出現同時寫或讀寫交疊。
同樣在finally中釋放鎖。
三、內部原理
3.1 鎖實現及獲取
ReentrantReadWriteLock內部并不是維護兩把完全獨立的鎖,而是用一個32位int變量state同時記錄讀寫狀態(tài):
其中高16位表示讀鎖計數,允許多個線程同時增加。
低16位表示寫鎖計數,同一個線程可以重入。
ReentrantReadWriteLock中相關的核心常量:

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

