ReadWriteLock(讀寫鎖)和 StampedLock

1. ReadWriteLock(讀寫鎖):實現高性能緩存

總結:

要點

內容

適用場景

讀多寫少、高并發讀取場景(如緩存)

鎖類型

ReadWriteLock接口,ReentrantReadWriteLock實現

讀鎖 vs 寫鎖

多線程可同時讀,寫獨占

按需加載中的“二次檢查”

避免重復查詢數據庫

鎖升級

? 不支持

鎖降級

? 支持(寫鎖降為讀鎖)

數據一致性

可采用超時失效、Binlog 推送或雙寫策略

1.1. 讀寫鎖的概念

并發優化的場景:讀多寫少

  • 實際開發中,緩存常用于提升性能(比如緩存元數據、基礎數據)
  • 這類數據 讀取頻繁、寫入稀少,典型讀多寫少

常規鎖(互斥鎖)的限制

  • synchronizedReentrantLock 會限制所有線程串行訪問,即便是多個讀取操作
  • 性能瓶頸:多個讀線程也互相阻塞

ReadWriteLock的基本規則:

  1. 允許多個線程同時讀共享變量;
  2. 只允許一個線程寫共享變量;
  3. 如果一個寫線程正在執行寫操作,此時禁止讀線程讀共享變量。

Java 實現類:

  • 接口:ReadWriteLock
  • 實現:ReentrantReadWriteLock(支持可重入)

1.2. 封裝線程安全的緩存類

示例:Cache<K, V> 類(線程安全)


class Cache<K,V> {final Map<K, V> m = new HashMap<>();final ReadWriteLock rwl = new ReentrantReadWriteLock();final Lock r = rwl.readLock();final Lock w = rwl.writeLock();V get(K key) {r.lock();try { return m.get(key); }finally { r.unlock(); }}V put(String key, Data v) {w.lock();try { return m.put(key, v); }finally { w.unlock(); }}
}

緩存數據的加載策略

1. 一次性加載(適合數據量小)

  • 程序啟動時從源頭加載所有數據,調用 put() 寫入緩存
  • 簡單易行,示意圖如下:

2. 按需加載(懶加載,適合數據量大)

原理:

  • 查詢緩存時,如果緩存中沒有數據,則從源頭加載并更新緩存

實現邏輯(含二次檢查):

V get(K key) {
V v = null;
r.lock();                    // ① 獲取讀鎖
try { v = m.get(key); }      // ② 嘗試從緩存讀取
finally { r.unlock(); }      // ③ 釋放讀鎖if (v != null) return v;     // ④ 緩存命中w.lock();                    // ⑤ 獲取寫鎖
try {v = m.get(key);            // ⑥ 再次檢查if (v == null) {v = 查詢數據庫();         // ⑦ 查詢源數據m.put(key, v);           // 寫入緩存}
} finally {w.unlock();                // 釋放寫鎖
}
return v;
}

為什么要“再次驗證”?

  • 防止多個線程同時 miss 緩存,導致重復數據庫查詢(讀寫鎖是排他的)

1.3. 讀寫鎖的升級與降級

1. 不支持鎖的升級

不允許持有讀鎖時再獲取寫鎖(會死鎖)

錯誤代碼示例:

r.lock();
try {if (m.get(key) == null) {w.lock(); // ? 升級為寫鎖,阻塞try { m.put(key, 查詢數據庫()); }finally { w.unlock(); }}
} finally {r.unlock(); // 死鎖
}

2. 支持鎖的降級

持有寫鎖時,可以先獲取讀鎖,再釋放寫鎖

w.lock();         // 寫鎖
try {if (!cacheValid) {data = 查詢數據();cacheValid = true;r.lock();     // 降級為讀鎖}
} finally {w.unlock();     // 釋放寫鎖
}try {use(data);      // 仍持有讀鎖
} finally {r.unlock();     // 釋放讀鎖
}

補充:緩存一致性問題及解決方案

常見解決方式:

方式

描述

超時失效機制

每條緩存數據設定有效期,到期重新加載

Binlog 同步

數據庫變更觸發緩存更新(如 MySQL Binlog)

數據雙寫

同時寫入緩存和數據庫(需解決一致性問題)

2. StampedLock(比讀寫鎖更快)

  • StampedLock 提供 寫鎖、悲觀讀鎖、樂觀讀 三種模式;
  • 樂觀讀是 無鎖讀取 + 校驗機制,適合讀多寫少;
  • stamp 類似數據庫中的 version,用于一致性驗證;
  • 不支持重入、不支持條件變量、不支持中斷;
  • 使用不當可能造成 CPU 飆升問題。

2.1. StampedLock的概念

背景與作用

  • 傳統讀寫鎖(ReadWriteLock):適用于“讀多寫少”的場景,支持多個線程并發讀,但寫操作會阻塞所有讀操作。
  • StampedLock(JDK 1.8 新增)
    • 提供更高性能的讀寫控制機制;
    • 特別適合讀多寫少場景;
    • 支持 三種鎖模式,引入了性能更優的“樂觀讀

StampedLock的三種鎖模式:

鎖類型

特點

互斥性

適用場景

寫鎖

和寫鎖類似

與所有其他鎖互斥

修改共享數據

悲觀讀鎖

與 ReadLock 類似,可多個線程同時持有

與寫鎖互斥

讀取共享數據(有一定寫的可能性)

樂觀讀

無鎖!性能最好

可與寫鎖并發(需校驗)

讀取頻繁,修改極少場景

  • 加鎖后都會返回一個 stamp,釋放鎖時需要傳入。

代碼示例:

final StampedLock sl = new StampedLock();// 悲觀讀鎖
long stamp = sl.readLock();
try {// 讀取操作
} finally {sl.unlockRead(stamp);
}// 寫鎖
long stamp = sl.writeLock();
try {// 寫操作
} finally {sl.unlockWrite(stamp);
}

2.2. 樂觀讀原理與用法

樂觀讀流程:

  1. 調用 tryOptimisticRead() 獲取 stamp;
  2. 讀取共享變量到局部變量(期間數據可能被其他線程寫操作修改!);
  3. 通過 validate(stamp) 判斷是否有寫操作發生;
    • 若返回 true,說明無寫操作,讀取有效;
    • 若返回 false,則需“升級為悲觀讀鎖”。

示例代碼:

long stamp = sl.tryOptimisticRead();
int curX = x, curY = y;
if (!sl.validate(stamp)) {stamp = sl.readLock(); // 升級為悲觀讀try {curX = x;curY = y;} finally {sl.unlockRead(stamp);}
}
return Math.sqrt(curX * curX + curY * curY);

為什么比 ReadWriteLock 更快?

  • 樂觀讀無鎖,不阻塞寫操作;
  • 只有在檢測到寫入發生時,才升級為悲觀讀,大大減少了鎖競爭和阻塞

使用注意事項

注意點

說明

? 不支持重入

StampedLock不是可重入鎖(不可 Reentrant)

? 不支持條件變量

不能用 await/signal等 等待通知機制

? 不支持中斷

調用 interrupt()可能導致 CPU 飆升至 100%,應避免

對比數據庫樂觀鎖

  • 數據庫中通過 version 字段實現樂觀鎖控制;
  • 讀取時返回 version,更新時用 where version=舊值 控制;
  • 與 StampedLock 的 stamp 機制非常相似,便于理解樂觀讀校驗的本質。

2.3. 使用模板

1. 讀操作模板:

long stamp = sl.tryOptimisticRead();
// 讀取局部變量
...
if (!sl.validate(stamp)) {stamp = sl.readLock();try {...} finally {sl.unlockRead(stamp);}
}// 使用局部變量...

2. 寫操作模板:

long stamp = sl.writeLock();
try {// 修改共享變量...
} finally {sl.unlockWrite(stamp);
}

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

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

相關文章

【決勝公務員考試】求職OMG——見面課測驗1

2025最新版&#xff01;&#xff01;&#xff01;6.8截至答題&#xff0c;大家注意呀&#xff01; 博主碼字不易點個關注吧,祝期末順利~~ 1.單選題(2分) 下列說法錯誤的是:&#xff08; B &#xff09; A.選調生屬于公務員系統 B.公務員屬于事業編 C.選調生有基層鍛煉的要求 D…

vue3 el-button 自定義本地圖標

設置不生效的原因可能有&#xff1a;1.style標簽里沒加scoped <style scoped></style>2.本地圖片路徑指向錯誤3.自定義圖片長寬沒設置4.deep深度選擇器使用錯誤&#xff0c;vue3用:deep() <el-tooltip content"重新匹配" placement"top"&g…

如何在最短時間內提升打ctf(web)的水平?

剛剛刷完2遍 bugku 的 web 題&#xff0c;前來答題。 每個人對刷題理解是不同&#xff0c;有的人是看了writeup就等于刷了&#xff0c;有的人是收藏了writeup就等于刷了&#xff0c;有的人是跟著writeup做了一遍就等于刷了&#xff0c;還有的人是獨立思考做了一遍就等于刷了。…

6.8 note

paxos算法_初步感知 Paxos算法保證一致性主要通過以下幾個關鍵步驟和機制&#xff1a; 準備階段 - 提議者向所有接受者發送準備請求&#xff0c;請求中包含一個唯一的編號。 - 接受者收到請求后&#xff0c;會檢查編號&#xff0c;如果編號比它之前見過的都大&#xff0c;就會承…

c++ openssl 使用 DES(數據加密標準)進行加密和解密的基本操作

使用 DES&#xff08;數據加密標準&#xff09;進行加密和解密的基本操作&#xff0c;重點展示了 ECB 和 CBC 模式&#xff0c;并且通過篡改密文的方式來進行攻擊。下面是對每個部分的詳細解析。 1. 結構體 Slip struct Slip {char from[16] { 0 }; // 交易的發起者&#x…

OpenWrt:使用ALSA實現邊錄邊播

ALSA是Linux系統中的高級音頻架構&#xff08;Advanced Linux Sound Architecture&#xff09;。目前已經成為了linux的主流音頻體系結構&#xff0c;想了解更多的關于ALSA的知識&#xff0c;詳見&#xff1a;http://www.alsa-project.org 在內核設備驅動層&#xff0c;ALSA提供…

【.net core】天地圖坐標轉換為高德地圖坐標(WGS84 坐標轉 GCJ02 坐標)

類文件 public static class WGS84ToGCJ02Helper {// 定義一些常量private const double PI 3.14159265358979324;private const double A 6378245.0;private const double EE 0.00669342162296594323;// 判斷坐標是否在中國范圍內&#xff08;不在國內則不進行轉換&#x…

Matlab自學筆記五十七:符號運算、可變精度運算、雙精度浮點型運算,三種運算精度的概念、比較、選擇和應用

1.可變精度算術的概念 默認的&#xff0c;Matlab雙精度浮點數使用16位數字精度&#xff0c;而符號數學工具箱的vpa函數&#xff0c;提供了無限大的可變精度&#xff0c;它默認使用32位數字精度&#xff0c;32位指的是有效數字的位數&#xff1b; 2.具體用法 程序示例&#x…

由匯編代碼確定switch語句

int switch2(int x) {int result0;switch(x){/* switch語句主體缺失 */}return result; }在編譯函數時&#xff0c;GCC為程序的初始部分以及跳轉表生成了如下匯編代碼。 1 MOVL 8(%ebp), %eax ;x位于相對于寄存器%ebp偏移量為8的地方。 2 ADDL $2, %eax …

java 使用HanLP 入門教程

1. 安裝 HanLP Maven 依賴 <dependency><groupId>com.hankcs</groupId><artifactId>hanlp</artifactId><version>portable-1.8.4</version> <!-- 最新版本請查看官網 --> </dependency>注意&#xff1a;portable 版本…

vm虛擬機添加虛擬機無反應,獲取所有權

問題描述 虛擬機忘記關機&#xff0c;就把電腦關了&#xff0c;早上打開用不了了&#xff0c;重新添加&#xff0c;也沒反應&#xff0c;獲取所有權后就沒了 問題解決 將虛擬機文件目錄下的.lck文件夾&#xff0c;刪除&#xff0c;或者改個名&#xff0c;我是改為了.backup方…

為何選擇Spring框架學習設計模式與編碼技巧?

&#x1f4cc; 結論先行 推薦項目&#xff1a;Spring Framework 推薦理由&#xff1a;設計模式覆蓋全面 編碼技巧教科書級實現 Java 生態基石地位 &#x1f3c6; 三維度對比分析 維度SpringMyBatisXXL-JOB設計模式??????????代碼抽象??????????生態價…

MySQL 索引:聚集索引與二級索引

在數據庫性能優化的征途中&#xff0c;索引無疑扮演著至關重要的角色。正確理解和使用索引&#xff0c;能夠顯著提升查詢效率&#xff0c;為應用帶來絲滑般的操作體驗。今天&#xff0c;我們將深入 MySQL 的心臟&#xff0c;重點探討 InnoDB 存儲引擎中兩種核心的索引類型&…

【Elasticsearch】映射:詳解 _source store 字段

映射&#xff1a;詳解 _source & store 字段 1._source 字段1.1 特點1.2 示例 2.store 字段2.1 特點2.2 示例 3.兩者對比3.1 使用建議3.2 實際應用示例 1._source 字段 _source 是 Elasticsearch 中一個特殊的元字段&#xff0c;它存儲了文檔在索引時的原始 JSON 內容。 …

新建網站部署流程

1. 新建 Node 服務&#xff0c;指定端口并代理前端靜態資源 操作步驟&#xff1a; 初始化 Node 項目mkdir my-website && cd my-website npm init -y npm install express創建 app.js&#xff08;示例代碼&#xff09;const express require(express); const app e…

時序數據庫IoTDB結合SeaTunnel實現高效數據同步

益、基本概念介紹 1.1 Apache IoTDB Apache IoTDB是一款專為工業物聯網設計的時序數據庫管理系統&#xff0c;集數據收集、存儲、管理與分析于一體&#xff0c;滿足海量數據存儲、高速讀取及復雜數據分析需求。其架構包括時序文件&#xff08;TsFile&#xff09;、數據庫引擎…

k8s業務程序聯調工具-KtConnect

概述 原理 工具作用是建立了一個從本地到集群的單向VPN&#xff0c;根據VPN原理&#xff0c;打通兩個內網必然需要借助一個公共中繼節點&#xff0c;ktconnect工具巧妙的利用k8s原生的portforward能力&#xff0c;簡化了建立連接的過程&#xff0c;apiserver間接起到了中繼節…

RFID推動新能源汽車零部件生產系統管理應用案例

RFID推動新能源汽車零部件生產系統管理應用案例 一、項目背景 新能源汽車零部件場景 在新能源汽車零部件生產領域&#xff0c;電子冷卻水泵等關鍵部件的裝配溯源需求日益增長。傳統 RFID 溯源方案采用 “網關 RFID 讀寫頭” 模式&#xff0c;存在單點位單獨頭溯源、網關布線…

C#封裝HttpClient:HTTP請求處理最佳實踐

C#封裝HttpClient&#xff1a;HTTP請求處理最佳實踐 在現代的.NET應用程序開發中&#xff0c;與外部服務進行HTTP通信是一項常見需求。HttpClient作為.NET框架中處理HTTP請求的核心組件&#xff0c;為我們提供了強大而靈活的API。然而&#xff0c;直接使用原生的HttpClient可能…

【Redis/2】核心特性、應用場景與安裝配置

文章目錄 一、初識 Redis1.1 Redis 概述1. Redis 簡介2. Redis 的發展歷程 1.2 Redis 核心特性1. 高性能2. 豐富的數據類型3. 持久化4. 原子操作5. 主從復制6. 高可用性與分布式7. 內存存儲與低延遲8. 靈活的過期策略9. 事務支持10. 簡單的 API總結 1.3 Redis 應用場景Redis 適…