深入理解讀寫鎖 ReadWriteLock

在高性能并發編程中,如何有效地管理共享資源的訪問是核心挑戰之一。傳統的排他鎖(如ReentrantLock)在讀多寫少的場景下,性能瓶頸尤為突出,因為它不允許并發讀取。Java并發包(java.util.concurrent.locks)提供的ReadWriteLock接口及其實現ReentrantReadWriteLock,以及分布式鎖框架Redisson提供的RReadWriteLock,為解決這一問題提供了高效的解決方案。

1. 引言

隨著多核處理器和分布式系統的普及,并發編程已成為現代軟件開發不可或缺的一部分。在多線程環境中,對共享資源的正確訪問是確保數據一致性和程序穩定性的關鍵。然而,不恰當的鎖機制可能導致性能瓶頸,尤其是在讀操作遠多于寫操作的場景下。例如,一個緩存系統,其數據被頻繁讀取,但更新頻率較低。如果使用synchronized關鍵字或ReentrantLock這樣的排他鎖,即使是多個線程同時讀取數據,也必須串行執行,這極大地限制了系統的并發能力。

為了解決這一問題,Java引入了讀寫鎖(ReadWriteLock)的概念。讀寫鎖允許多個讀線程同時訪問共享資源,從而提高并發性;但在有寫線程訪問時,所有讀寫操作都將被阻塞,以保證數據的一致性。這種“讀-讀共享,讀-寫互斥,寫-寫互斥”的特性,使得讀寫鎖成為處理讀多寫少場景的理想選擇。

2. ReadWriteLock 核心概念與原理

2.1 什么是ReadWriteLock

ReadWriteLock是Java java.util.concurrent.locks包中定義的一個接口,它維護了一對相關的鎖:一個用于讀操作的鎖(ReadLock)和一個用于寫操作的鎖(WriteLock)。其核心思想是區分讀操作和寫操作,并對它們施加不同的并發控制策略:

  • 讀鎖(ReadLock):是共享鎖。在沒有寫鎖被持有的情況下,多個線程可以同時獲取讀鎖。這意味著,只要沒有線程正在修改數據,任意數量的線程都可以并發地讀取數據,從而顯著提高并發性能。
  • 寫鎖(WriteLock):是排他鎖。只有當沒有任何讀鎖或寫鎖被持有時,寫鎖才能被一個線程獲取。一旦寫鎖被持有,所有后續的讀鎖和寫鎖請求都將被阻塞,直到寫鎖被釋放。這確保了在數據修改期間,數據的一致性和完整性。

2.2 ReadWriteLock 的優勢

相較于傳統的排他鎖,ReadWriteLock在讀多寫少的場景下具有顯著優勢:

  • 提高并發性:允許多個讀線程同時訪問共享資源,充分利用多核處理器的能力,提高系統的吞吐量。
  • 避免寫饑餓:雖然讀鎖可以并發獲取,但ReadWriteLock的實現通常會確保寫操作最終能夠獲得鎖,避免寫線程長時間等待而無法執行(盡管在某些非公平實現中,寫線程仍可能面臨饑餓問題)。
  • 簡化編程模型:通過明確區分讀寫操作,使開發者能夠更直觀地設計并發訪問邏輯,降低了并發編程的復雜性。

2.3 ReentrantReadWriteLock:Java內置實現

ReentrantReadWriteLockReadWriteLock接口的一個具體實現,它提供了可重入的讀寫鎖功能。可重入性意味著,如果一個線程已經持有了讀鎖,它可以再次獲取讀鎖;同樣,如果一個線程持有了寫鎖,它可以再次獲取寫鎖,并且在持有寫鎖的情況下,也可以獲取讀鎖(鎖降級)。

ReentrantReadWriteLock的內部實現基于AQS(AbstractQueuedSynchronizer)框架,通過一個int類型的狀態變量來表示讀鎖和寫鎖的持有情況。狀態變量的高16位用于表示讀鎖的計數,低16位用于表示寫鎖的計數。這種設計使得讀寫鎖的獲取和釋放操作能夠高效地進行。

特性:

  • 可重入性:讀鎖和寫鎖都支持可重入。一個線程在持有讀鎖的情況下可以再次獲取讀鎖,持有寫鎖的情況下可以再次獲取寫鎖。此外,持有寫鎖的線程可以獲取讀鎖(鎖降級),但持有讀鎖的線程不能直接獲取寫鎖(鎖升級,會導致死鎖)。
  • 公平性選擇ReentrantReadWriteLock支持公平(fair)和非公平(nonfair)兩種模式。在公平模式下,等待時間最長的線程將優先獲取鎖;在非公平模式下,則允許插隊,這通常能帶來更高的吞吐量,但可能導致饑餓問題。
  • 鎖降級:寫鎖可以降級為讀鎖。即一個線程在持有寫鎖的情況下,可以先獲取讀鎖,然后釋放寫鎖。這在更新數據后需要讀取最新數據的場景中非常有用,可以避免在讀寫切換過程中釋放所有鎖導致其他線程修改數據。

3. Redisson 分布式讀寫鎖(RReadWriteLock)

在分布式系統中,單機ReadWriteLock無法滿足跨JVM進程的并發控制需求。Redisson作為一款基于Redis的Java駐內存數據網格(In-Memory Data Grid)和分布式對象框架,提供了RReadWriteLock來實現分布式環境下的讀寫鎖。RReadWriteLock實現了java.util.concurrent.locks.ReadWriteLock接口,因此其API與單機版保持一致,使得從單機到分布式的遷移變得平滑。

3.1 Redisson RReadWriteLock 的實現原理

Redisson的RReadWriteLock底層基于Redis的原子操作和發布/訂閱機制實現。其核心原理如下:

  • 寫鎖:當一個客戶端嘗試獲取寫鎖時,Redisson會在Redis中設置一個帶有過期時間的鍵(通常是一個哈希表),表示寫鎖被持有。如果該鍵已經存在,則表示寫鎖已被其他客戶端持有,當前客戶端將進入等待隊列。為了保證原子性,寫鎖的獲取和釋放通常通過Lua腳本在Redis服務器端執行。
  • 讀鎖:當一個客戶端嘗試獲取讀鎖時,Redisson會在Redis中記錄讀鎖的持有者信息(通常也是一個哈希表中的字段)。多個客戶端可以同時記錄讀鎖信息。當寫鎖被持有或有寫鎖在等待時,讀鎖的獲取將被阻塞。讀鎖的釋放同樣通過Lua腳本實現。
  • 鎖重入與鎖降級:Redisson通過在Redis中維護每個客戶端的鎖重入計數來實現可重入性。鎖降級(寫鎖降級為讀鎖)也通過原子操作實現,確保在降級過程中不會出現數據不一致。
  • 看門狗機制:為了防止客戶端崩潰導致鎖無法釋放,Redisson提供了看門狗(Watchdog)機制。當客戶端成功獲取鎖后,Redisson會啟動一個定時任務,定期延長鎖的過期時間,直到鎖被釋放。如果客戶端崩潰,看門狗任務停止,鎖會在過期時間后自動釋放。

3.2 Redisson RReadWriteLock 的優勢

  • 分布式支持:解決了傳統ReadWriteLock無法在分布式環境下使用的限制,實現了跨JVM進程的并發控制。
  • 高可用性:基于Redis集群或主從復制,Redisson的讀寫鎖具有較高的可用性。
  • 性能優化:通過Redis的內存操作和原子性,以及Redisson的優化,提供了高性能的分布式鎖服務。
  • API兼容性:實現了java.util.concurrent.locks.ReadWriteLock接口,降低了學習成本和遷移成本。

4. 代碼案例:ReentrantReadWriteLock 與 Redisson RReadWriteLock

為了更好地理解讀寫鎖的使用,我們將分別展示ReentrantReadWriteLockRReadWriteLock的代碼示例。假設我們有一個簡單的計數器,需要支持并發讀寫。

4.1 單機環境:使用 ReentrantReadWriteLock

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;public class CounterWithReadWriteLock {private final ReadWriteLock rwLock = new ReentrantReadWriteLock();private int count = 0;public int getCount() {rwLock.readLock().lock(); // 獲取讀鎖try {System.out.println(Thread.currentThread().getName() + " 正在讀取,當前值為: " + count);// 模擬讀取耗時操作Thread.sleep(50);return count;} catch (InterruptedException e) {Thread.currentThread().interrupt();return -1;} finally {rwLock.readLock().unlock(); // 釋放讀鎖}}public void increment() {rwLock.writeLock().lock(); // 獲取寫鎖try {int oldValue = count;count++;System.out.println(Thread.currentThread().getName() + " 正在寫入,值從 " + oldValue + " 變為: " + count);// 模擬寫入耗時操作Thread.sleep(100);} catch (InterruptedException e) {Thread.currentThread().interrupt();} finally {rwLock.writeLock().unlock(); // 釋放寫鎖}}public static void main(String[] args) {CounterWithReadWriteLock counter = new CounterWithReadWriteLock();// 創建多個讀線程for (int i = 0; i < 5; i++) {new Thread(() -> {for (int j = 0; j < 3; j++) {counter.getCount();}}, "Reader-" + i).start();}// 創建一個寫線程new Thread(() -> {for (int i = 0; i < 2; i++) {counter.increment();}}, "Writer-0").start();// 創建另一個寫線程new Thread(() -> {for (int i = 0; i < 2; i++) {counter.increment();}}, "Writer-1").start();}
}

代碼說明:

  • rwLock.readLock().lock():獲取讀鎖。多個讀線程可以同時進入getCount()方法。
  • rwLock.writeLock().lock():獲取寫鎖。當寫線程進入increment()方法時,所有讀線程和寫線程都將被阻塞。
  • finally塊確保鎖的正確釋放,避免死鎖。

運行上述代碼,您會觀察到多個“Reader”線程可以同時打印讀取信息,而“Writer”線程在寫入時會阻塞其他所有讀寫操作,體現了讀寫鎖的特性。

4.2 分布式環境:使用 Redisson RReadWriteLock

首先,確保您的項目中已引入Redisson的Maven依賴:

<dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.27.1</version> <!-- 使用最新穩定版本 -->
</dependency>

然后,是使用RReadWriteLock的示例代碼:

import org.redisson.Redisson;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;public class DistributedCounterWithRedissonReadWriteLock {private static RedissonClient redissonClient;private static final String LOCK_KEY = "myDistributedCounterLock";private static int count = 0; // 模擬共享資源,實際生產中應存儲在Redis或其他共享存儲中public static void initRedisson() {Config config = new Config();// 單機模式,根據實際Redis部署情況配置config.useSingleServer().setAddress("redis://127.0.0.1:6379");// 如果Redis有密碼,可以設置:.setPassword("your_password");redissonClient = Redisson.create(config);System.out.println("Redisson客戶端初始化成功。");}public static void shutdownRedisson() {if (redissonClient != null) {redissonClient.shutdown();System.out.println("Redisson客戶端已關閉。");}}public static int getCount() {RReadWriteLock rwLock = redissonClient.getReadWriteLock(LOCK_KEY);rwLock.readLock().lock(); // 獲取讀鎖try {System.out.println(Thread.currentThread().getName() + " 正在讀取,當前值為: " + count);// 模擬讀取耗時操作Thread.sleep(50);return count;} catch (InterruptedException e) {Thread.currentThread().interrupt();return -1;} finally {rwLock.readLock().unlock(); // 釋放讀鎖}}public static void increment() {RReadWriteLock rwLock = redissonClient.getReadWriteLock(LOCK_KEY);rwLock.writeLock().lock(); // 獲取寫鎖try {int oldValue = count;count++;System.out.println(Thread.currentThread().getName() + " 正在寫入,值從 " + oldValue + " 變為: " + count);// 模擬寫入耗時操作Thread.sleep(100);} catch (InterruptedException e) {Thread.currentThread().interrupt();} finally {rwLock.writeLock().unlock(); // 釋放寫鎖}}public static void main(String[] args) throws InterruptedException {initRedisson();// 模擬多個JVM進程或多個線程并發訪問// 為了演示方便,這里在一個JVM中創建多個線程// 實際分布式場景中,這些線程可能運行在不同的服務器上// 創建多個讀線程for (int i = 0; i < 5; i++) {new Thread(() -> {for (int j = 0; j < 3; j++) {getCount();}}, "Distributed-Reader-" + i).start();}// 創建一個寫線程new Thread(() -> {for (int i = 0; i < 2; i++) {increment();}}, "Distributed-Writer-0").start();// 創建另一個寫線程new Thread(() -> {for (int i = 0; i < 2; i++) {increment();}}, "Distributed-Writer-1").start();// 等待所有線程執行完畢Thread.sleep(5000); // 適當等待,確保所有線程有機會執行shutdownRedisson();}
}

代碼說明:

  • initRedisson():初始化Redisson客戶端,連接到Redis服務器。請根據您的Redis部署情況修改setAddress
  • redissonClient.getReadWriteLock(LOCK_KEY):通過一個唯一的鍵名獲取分布式讀寫鎖實例。
  • rwLock.readLock().lock()rwLock.writeLock().lock():與單機版API完全一致,Redisson在底層處理了分布式同步的復雜性。
  • count變量在此示例中仍為JVM內部變量,但在實際分布式應用中,count的值應存儲在Redis或其他共享存儲中,并通過Redisson的分布式對象(如RAtomicLong)進行操作,以確保真正的分布式一致性。

5. 最佳實踐與注意事項

  • 選擇合適的鎖:讀寫鎖并非適用于所有場景。在寫操作頻繁的場景下,讀寫鎖的開銷可能大于其帶來的收益,此時傳統的排他鎖或更細粒度的鎖可能更合適。
  • 最小化鎖的范圍:無論是讀鎖還是寫鎖,都應盡可能地縮小其作用范圍,只在真正需要保護共享資源的代碼塊中加鎖,以減少鎖的持有時間,提高并發性。
  • 避免鎖升級:在持有讀鎖的情況下嘗試獲取寫鎖會導致死鎖。如果需要從讀模式切換到寫模式,應先釋放讀鎖,再獲取寫鎖(這可能導致其他線程在中間修改數據),或者使用鎖降級(寫鎖降級為讀鎖)的模式。
  • 處理中斷:在獲取鎖時,應考慮處理線程中斷異常(InterruptedException),以確保程序的健壯性。
  • Redisson配置:在分布式環境下使用Redisson時,正確配置Redisson客戶端至關重要,包括Redis地址、密碼、連接池大小等。對于生產環境,建議使用Redis集群或哨兵模式以保證高可用。
  • 共享資源同步:使用RedissonRReadWriteLock時,請記住它只解決了鎖的同步問題,共享資源本身(如示例中的count)也需要存儲在分布式存儲中,并使用Redisson提供的分布式數據結構來保證其在不同節點間的一致性。

6. 總結

ReadWriteLock是Java并發編程中一個強大的工具,它通過區分讀寫操作,在讀多寫少的場景下顯著提升了系統的并發性能。ReentrantReadWriteLock提供了單機環境下的高效實現,而Redisson的RReadWriteLock則將讀寫鎖的能力擴展到了分布式系統,使得跨JVM進程的并發控制成為可能。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/86109.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/86109.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/86109.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Unity Addressable使用之檢測更新流程

補充知識 關鍵文件說明 Addressable打包后會生成多種文件&#xff0c;主要包括 .hash、.json 和 .bundle 文件&#xff0c;它們各自有不同的作用。 .hash 文件&#xff08;哈希文件&#xff09; 作用&#xff1a; 用于 版本對比&#xff0c;檢查資源是否有更新。存儲的是 資…

Elasticsearch 中實現推薦搜索(方案設想)

1. 存儲商品數據的數據類型 為了支持推薦搜索&#xff0c;商品數據通常需要包含以下字段&#xff1a; 商品索引結構 PUT /products {"mappings": {"properties": {"product_id": {"type": "keyword" // 商品 ID},"…

Aerotech系列(4)Aerotech.A3200名空間

IconTypeDescriptionAxisMask Represents a selection of axes Controller Represents a controller Allows configuring and c

React Router 是怎么實現靈活導航的?

&#x1f399; 歡迎來到《前端達人 React播客書單》第 21 期。 視頻版&#xff08;播客風格更精彩&#xff09; 今天我們不講 Hook&#xff0c;來拆解前端開發中另一個高頻組件&#xff1a;React Router 的進階導航模式。 你可能用過 <Link> 或 <Route>&#xff0…

Modbus TCP轉Profibus DP網關與JF - 600MT 稱重變送器輕松實現數據互換

Modbus TCP轉Profibus DP網關與JF - 600MT 稱重變送器輕松實現數據互換 在工業自動化領域&#xff0c;不同設備之間的通信與數據交互至關重要。Modbus TCP轉Profibus DP網關作為連接不同協議設備的關鍵橋梁&#xff0c;發揮著不可或缺的作用。本文將以JF - 600MT稱重變送器與3…

聊聊 SQL 注入那些事兒

相信大家對于學校們糟糕的網絡環境和運維手段都早有體會&#xff0c;在此就不多做吐槽了。今天我們來聊一聊SQL注入相關的內容。 何謂SQL注入&#xff1f; SQL注入是一種非常常見的數據庫攻擊手段&#xff0c;SQL注入漏洞也是網絡世界中最普遍的漏洞之一。大家也許都聽過某某學…

多傳感器融合

目錄 多傳感器融合 多傳感器融合的方向 傳感器融合方案介紹 LOAM LIO-SAM LVI-SAM 多線激光雷達性質 什么是運動畸變 兩步優化的幀間里程記 IMU 器件介紹及選型建議 IMU 標定方法簡介 視覺里程計 VS 激光里程計 LVI-SAM 激光視覺融合思路簡介 多傳感器融合工程實踐經驗與技巧 多…

Auto-GPT vs ReAct:兩種智能體思路對決

目錄 Auto-GPT vs ReAct&#xff1a;兩種智能體思路對決 &#x1f9e0; 一、智能體的演化背景 &#x1f9e9; 二、Auto-GPT&#xff1a;自循環的執行體 &#x1f50d; 三、ReAct&#xff1a;推理 行動的交錯協同 ?? 四、對比總結 &#x1f6e0; 五、你該選誰&#xff…

本地部署大模型性能測試,DeepSeek-R1-0528-Qwen-8B 依然是我的不二之選

大家好&#xff0c;我是 ai 學習的老章 介紹一個大模型并發性能測試工具 看一下我高頻使用的&#xff0c;在2*4090顯卡上部署的 DeepSeek-R1-0528-Qwen-8B 性能如何 _我_特別喜歡的三個DeepSeek版本 DeepSeek-R1-0528 蒸餾 Qwen3:8B 大模型&#xff0c;雙 4090 本地部署&am…

華為云Flexus+DeepSeek征文|華為云 Dify 高可用部署教程:CCE 容器集群一鍵構建企業級智能應用

前言 在數字化轉型加速的企業級應用場景中&#xff0c;構建高可用智能平臺已成為業務創新的核心驅動力。本文深度解析基于華為云CCE容器服務的Dify智能應用部署實踐&#xff0c;揭示如何通過云原生架構與AI技術的深度融合&#xff0c;實現企業知識管理、智能客服等場景的敏捷落…

Linux 多進程間通信(IPC)詳解

在 Linux 系統中,多進程通信(Inter-Process Communication, IPC) 是實現多個進程之間數據交換和同步的重要機制。由于每個進程擁有獨立的地址空間,因此需要借助特定的系統機制來實現信息共享。 ?? Linux 下常見的 6 種進程間通信方式 管道(Pipe)命名管道(FIFO)消息隊…

服務器數據恢復——異常斷電導致服務器故障的數據恢復案例

服務器數據恢復環境&#xff1a; 某服務器上有一組由12塊硬盤組建的raid5磁盤陣列。 機房供電不穩定導致機房中該服務器非正常斷電&#xff0c;重啟服務器后管理員發現服務器無法正常使用。 意外斷電可能會導致服務器上的raid模塊損壞。 服務器數據恢復過程&#xff1a; 1、將故…

微信小程序中 rpx與px的區別

在微信小程序中的rpx比px方便的多 <!--pages/welcome/welcome.wxml--> <!--rpx替換px--> <image style"width:200rpx;height: 200rpx"src"/images/avatar/3.png"></image> <text>你好&#xff0c;凍梨</text> <but…

python3實現QQ官方機器人回調驗證

考慮到第三方的機器人現在越來越難維持了&#xff0c;來搗鼓一下官方的機器人。雖然官方藏著掖著不肯開放很多功能&#xff0c;但起碼能用。官方機器人的優點是穩定&#xff0c;只要申請成功&#xff0c;且你自己不亂搞&#xff0c;基本不存在被封的可能&#xff0c;缺點是藤子…

基于Vue3+TS的自定義指令開發與業務場景應用

文章目錄 1. 前言2. 基礎概念與優勢?3. Vue3TS自定義指令的創建與注冊?3.1. 創建自定義指令?3.2. 注冊自定義指令? 4. 實際場景示例?4.1. 權限指令控制?4.2. 圖片懶加載指令? 5. 優化與注意事項? 1. 前言 在 Vue3 的開發生態中&#xff0c;自定義指令是一項極為靈活且…

Elasticsearch 索引文檔的流程

Elasticsearch 索引文檔的流程是一個分布式、多階段的過程&#xff0c;涉及客戶端請求、路由、主副本同步及持久化等步驟&#xff0c;具體流程如下&#xff1a; 一、客戶端請求與路由 1.1 文檔接收與路由計算? 客戶端通過 REST API 發送文檔寫入請求&#xff0c;需指…

【unity】批量剔除圖片四周空白像素的工具

摘要&#xff1a;Unity圖片空白像素批量處理工具 該工具提供兩種方式批量剔除圖片空白像素&#xff1a; 靜態處理類&#xff1a;提供TrimTexture方法&#xff0c;可讀取紋理像素數據&#xff0c;計算非透明區域邊界&#xff0c;生成裁剪后的新紋理&#xff1b;SaveTexture方法…

可編輯64頁PPT | 基于DeepSeek的數據治理方案

薦言摘要&#xff1a;在數據量爆炸式增長且業務需求日益復雜的當下&#xff0c;企業數據治理面臨著數據分散、標準混亂、價值挖掘難等諸多挑戰。我們基于DeepSeek強大的智能能力&#xff0c;為企業量身打造創新數據治理方案。 DeepSeek憑借其卓越的自然語言處理和深度學習技術…

啟用AWS VPC流日志保存到CloudWatch日志組

目標 啟用VPC流日志 啟用流日志 選擇vpc&#xff0c;開始啟用流日志&#xff0c;如下圖&#xff1a; 設置名稱和日志組&#xff0c;創建流日志&#xff0c;如下圖&#xff1a; 參考 AWS云中的VPC啟用流日志保存S3&#xff08;AWS中國云&#xff09;創建發布到 CloudWatc…

游戲引擎學習路徑與技術棧指南

游戲引擎架構全景圖&#xff08;基于GAMES104 V2.2思維導圖&#xff09; graph TDA[基礎架構] --> A1[面向數據管理]A --> A2[任務系統]A1 --> A11[ECS架構]A1 --> A12[內存優化]A2 --> A21[Job System]A2 --> A22[依賴調度]B[工具鏈] --> B1[編輯器框架]…