【Redisson 加鎖源碼解析】

Redisson 源碼解析 —— 分布式鎖實現過程

在分布式系統中,分布式鎖 是非常常見的需求,用來保證多個節點之間的互斥操作。Redisson 是 Redis 的一個 Java 客戶端,它提供了對分布式鎖的良好封裝。本文將從源碼角度剖析 Redisson 的分布式鎖實現過程。


一、分布式鎖的基本需求

一個健壯的分布式鎖需要滿足以下條件:

  1. 互斥性:同一時間只能有一個客戶端持有鎖。
  2. 死鎖避免:客戶端宕機后,鎖不會永久被占用。
  3. 可重入性:同一線程可多次獲取同一把鎖。
  4. 高可用性:在 Redis 集群模式下仍能正常工作。
  5. 超時釋放:設置持有鎖時間,時間超過鎖釋放,避免死鎖。
  6. 鎖時間續約:看門狗機制,避免業務未執行完畢鎖釋放,導致并發問題。

二、Redisson 分布式鎖的核心實現類以及加鎖方法

在源碼中,Redisson 提供了多種鎖的實現,最核心的是:

  • RedissonLock —— 基于 Redis 的可重入鎖實現
  • RedissonReadWriteLock —— 讀寫鎖
  • RedissonFairLock —— 公平鎖

我們主要關注 RedissonLock 的實現。


 RLock lock = redissonClient.getLock("32r");lock.方法名()

常用加鎖方法:
在這里插入圖片描述

  1. lock():獲取鎖,獲取不到會一致阻塞直到獲取。通過看門狗機制續期,默認持有鎖是30s,每隔10s續期一次。
  2. lock(long l, TimeUnit timeUnit):獲取鎖,獲取不到會一致阻塞直到獲取。持有鎖時間是手動入參的timeUnit,到期釋放鎖。
  3. tryLock(long waite, long l1, TimeUnit timeUnit) :獲取鎖失敗后,自旋,等待 waite 秒,獲取不到返回false,獲取到,持有鎖時間是 l1,單位 timeUnit。
  4. tryLock():嘗試獲取一次鎖,如果獲取不到,立即返回 false,獲取鎖成功,觸發 看門狗續期機制(和 lock() 一樣)。
  5. tryLock(long waitTime, TimeUnit unit):在 waitTime 時間窗口內,不斷嘗試執行,范圍內獲取鎖失敗,返回false。獲取成功,啟動看門狗機制。
 RLock lock = redissonClient.getLock("32r");

我們可以看到 redissonClient 調用這個方法時候,客戶端返回的是RedissonLock這個類
在這里插入圖片描述

所以對應的我們主要關注 RedissonLock 子類和父類RedissonBaseLock
在這里插入圖片描述

這里我主要分析 lock() 方法的調用,其他鎖的邏輯都是參考這個去完善的。

三、加鎖流程解析

1. 調用入口

當我們執行:

RLock lock = redisson.getLock("myLock");
lock.lock();

進入RedissonLock#lock方法:
在這里插入圖片描述
可以看到調用lock方法其實都是調用的另外一個lock(long leaseTime, TimeUnit unit, boolean interruptibly) 方法。
對應真正調用的lock()方法:

/*** 獲取分布式鎖的核心方法* @param leaseTime 鎖的租約時間* @param unit 時間單位* @param interruptibly 是否允許中斷* @throws InterruptedException 當線程被中斷時拋出*/
private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException {// 獲取當前線程ID,用于標識鎖的持有者long threadId = Thread.currentThread().getId();// 嘗試獲取鎖,返回剩余的TTL(生存時間)// 如果返回null表示獲取鎖成功,否則返回鎖的剩余過期時間Long ttl = this.tryAcquire(-1L, leaseTime, unit, threadId);// 如果ttl不為null,說明鎖獲取失敗,需要等待if (ttl != null) {// 訂閱鎖釋放的通知,返回一個Future對象CompletableFuture<RedissonLockEntry> future = this.subscribe(threadId);// 設置訂閱操作的超時時間this.pubSub.timeout(future);// 根據是否允許中斷來獲取訂閱結果RedissonLockEntry entry;if (interruptibly) {// 允許中斷的方式獲取結果entry = (RedissonLockEntry)this.commandExecutor.getInterrupted(future);} else {// 不允許中斷的方式獲取結果entry = (RedissonLockEntry)this.commandExecutor.get(future);}try {// 自旋等待鎖釋放while(true) {// 再次嘗試獲取鎖ttl = this.tryAcquire(-1L, leaseTime, unit, threadId);// 如果獲取鎖成功(ttl為null),則退出循環if (ttl == null) {return;}// 如果ttl大于等于0,說明鎖還存在,需要等待指定的時間if (ttl >= 0L) {try {// 使用信號量等待指定的ttl時間entry.getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);} catch (InterruptedException e) {// 如果允許中斷,直接拋出異常if (interruptibly) {throw e;}// 如果不允許中斷,繼續等待entry.getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);}} else {// 如果ttl小于0,表示需要無限等待if (interruptibly) {// 允許中斷的無限等待entry.getLatch().acquire();} else {// 不允許中斷的無限等待entry.getLatch().acquireUninterruptibly();}}}} finally {// 無論成功與否,都要取消訂閱,釋放資源this.unsubscribe(entry, threadId);}}
}

這時候我們只需要重點關注對應的this.tryAcquire(-1L, leaseTime, unit, threadId);這個方法。
源碼圖如下:
在這里插入圖片描述
對應的Java代碼解釋:

/*** 異步嘗試獲取鎖* @param waitTime 等待時間* @param leaseTime 鎖的租約時間* @param unit 時間單位* @param threadId 線程ID* @return 返回鎖的剩余TTL時間,null表示獲取鎖成功*/
private <T> RFuture<Long> tryAcquireAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {// 聲明TTL剩余時間的Future對象RFuture<Long> ttlRemainingFuture;// 判斷是否指定了租約時間if (leaseTime > 0L) {// 使用指定的租約時間嘗試獲取鎖ttlRemainingFuture = this.<Long>tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_LONG);} else {// 使用默認的內部鎖租約時間嘗試獲取鎖ttlRemainingFuture = this.<Long>tryLockInnerAsync(waitTime, this.internalLockLeaseTime, TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);}// 對獲取鎖的結果進行后續處理CompletionStage<Long> f = ttlRemainingFuture.thenApply((ttlRemaining) -> {// 如果ttlRemaining為null,說明成功獲取到鎖if (ttlRemaining == null) {// 判斷是否指定了租約時間if (leaseTime > 0L) {// 將指定的租約時間轉換為毫秒并存儲到內部鎖租約時間this.internalLockLeaseTime = unit.toMillis(leaseTime);} else {// 如果沒有指定租約時間,啟動鎖的自動續期機制// 防止鎖因過期而被誤釋放this.scheduleExpirationRenewal(threadId);}}// 返回TTL剩余時間(null表示獲取鎖成功,非null表示需要等待的時間)return ttlRemaining;});// 將CompletionStage包裝成RFuture并返回return new CompletableFutureWrapper(f);
}

這里最重要的是調用對應的tryAcquire里面的tryLockInnerAsync方法,方法詳解如下:

 <T> RFuture<T> tryLockInnerAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {return this.evalWriteAsync(this.getRawName(), LongCodec.INSTANCE, command,"if (redis.call('exists', KEYS[1]) == 0) then redis.call('hincrby', KEYS[1], ARGV[2], 1);redis.call('pexpire', KEYS[1], ARGV[1]); return nil; end; if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) thenredis.call('hincrby', KEYS[1], ARGV[2], 1); redis.call('pexpire', KEYS[1], ARGV[1]); return nil; end; return redis.call('pttl', KEYS[1]);", Collections.singletonList(this.getRawName()), new Object[]{unit.toMillis(leaseTime), this.getLockName(threadId)});}

這個tryLockInnerAsync方法主要是執行對應的腳本,然后返回剩余的時間,如果獲取鎖成功返回 nil ,獲取鎖失敗會返回 持有鎖的鎖過期時間

核心 Lua 腳本詳解如下:

Redisson 并不是簡單地 SETNX,而是使用 Lua 腳本 來保證操作的原子性
加鎖腳本大致邏輯如下:

if (redis.call('exists', KEYS[1]) == 0) then-- 鎖不存在,設置鎖并綁定到線程redis.call('hset', KEYS[1], ARGV[2], 1);redis.call('pexpire', KEYS[1], ARGV[1]);return nil;
end;-- 鎖已存在,判斷是否是當前線程重入
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) thenredis.call('hincrby', KEYS[1], ARGV[2], 1);redis.call('pexpire', KEYS[1], ARGV[1]);return nil;
end;return redis.call('pttl', KEYS[1]);

解釋:

  • KEYS[1]: 鎖的 key (如 myLock)
  • ARGV[1]: 鎖的過期時間(默認 30s)
  • ARGV[2]: 當前線程標識(由 UUID + 線程 ID 組成)

執行流程:

  1. 如果鎖不存在,設置 hash,key = 線程標識,value = 1。
  2. 如果鎖存在且是自己線程,則遞增重入次數。
  3. 否則返回鎖的剩余過期時間。

問題延伸:
Redis不是單線程嗎,高并發線程下不是線程安全嗎?為什么還需要使用Lua腳本保證原子性
想想為什么使用lua腳本,你可以想象一下高并發場景下,Redis執行命令是單線程的,Redis只能保證對應的單條命令是原子性的,不能保證多條命令的原子性,假設線程A執行:redis.call('exists', KEYS[1]) == 0結束后,線程B搶到執行權,然后線程B也執行:redis.call('exists', KEYS[1]) == 0,然后后續大家都會進行對應的鎖設置,導致線程A上鎖可能會被覆蓋,不過可以用hsetnx解決,但是后續可能判斷還是會有并發問題。使用 lua 腳本可以將多條命令整合成類似一條命令,redis執行,從而保證原子性

WatchDog 自動續期機制

Redisson 的一大亮點是 鎖續期機制

  • 當線程獲取鎖后,會啟動一個 看門狗定時任務,默認每隔 lockWatchdogTimeout / 3 秒續期一次(默認 30s → 10s)。
  • 如果業務邏輯執行很久,不用擔心鎖被提前釋放。
  • 如果線程宕機,定時任務不再執行,鎖會在超時后自動釋放。

判斷對應的leasetime有沒有指定,然后執行對應的續期或不續期的方法
源碼關鍵點在:scheduleExpirationRenewal() 方法。
關鍵代碼

   CompletionStage<Long> f = ttlRemainingFuture.thenApply((ttlRemaining) -> {if (ttlRemaining == null) {if (leaseTime > 0L) {this.internalLockLeaseTime = unit.toMillis(leaseTime);} else {this.scheduleExpirationRenewal(threadId);}}return ttlRemaining;});

根據對應的沒指定leaseTime ,然后執行對應的RedissonBaseLock#scheduleExpirationRenewal對應的方法邏輯如下:

  /*** 調度鎖的過期時間續期任務* 為指定線程啟動自動續期機制,防止鎖因過期而被誤釋放* @param threadId 需要續期的線程ID*/
protected void scheduleExpirationRenewal(long threadId) {// 創建新的過期時間管理條目ExpirationEntry entry = new ExpirationEntry();// 嘗試將新條目放入續期映射表中,如果已存在則返回舊條目// 使用putIfAbsent確保原子性操作,避免并發問題ExpirationEntry oldEntry = (ExpirationEntry)EXPIRATION_RENEWAL_MAP.putIfAbsent(this.getEntryName(), entry);// 判斷是否已經存在續期任務if (oldEntry != null) {// 如果已存在續期任務,只需將當前線程ID添加到現有條目中// 這種情況發生在同一個鎖被多個線程(可重入鎖)或同一線程多次獲取時oldEntry.addThreadId(threadId);} else {// 如果是首次為這個鎖創建續期任務// 將當前線程ID添加到新創建的條目中entry.addThreadId(threadId);try {// 啟動實際的續期任務// 這會創建定時任務,定期延長鎖的過期時間this.renewExpiration();} finally {// 檢查當前線程是否被中斷if (Thread.currentThread().isInterrupted()) {// 如果線程被中斷,取消剛剛啟動的續期任務// 防止資源泄漏和無效的續期操作this.cancelExpirationRenewal(threadId);}}}
}

這個通過一個創建一個ExpirationEntry 然后通過EXPIRATION_RENEWAL_MAP判斷是否存在,如果條目不存在就啟動對應的自動續期機制任務 renewExpiration()

RedissonBaseLock#renewExpiration()方法如下:

/*** 啟動鎖的自動續期機制* 創建定時任務,定期延長鎖的過期時間,防止鎖因超時而被釋放*/
private void renewExpiration() {// 從續期映射表中獲取當前鎖的過期時間管理條目ExpirationEntry ee = (ExpirationEntry)EXPIRATION_RENEWAL_MAP.get(this.getEntryName());// 如果條目存在,說明需要為這個鎖設置續期任務if (ee != null) {// 創建定時任務,在鎖租約時間的1/3處執行續期操作// 選擇1/3時間點是為了在鎖過期前有足夠的時間進行續期Timeout task = this.commandExecutor.getConnectionManager().newTimeout(new TimerTask() {public void run(Timeout timeout) throws Exception {// 定時任務執行時,重新獲取續期條目(防止在延遲期間被移除)ExpirationEntry ent = (ExpirationEntry)RedissonBaseLock.EXPIRATION_RENEWAL_MAP.get(RedissonBaseLock.this.getEntryName());// 雙重檢查:確保續期條目仍然存在if (ent != null) {// 獲取需要續期的第一個線程ID// 對于可重入鎖,可能有多個線程ID,取第一個進行續期Long threadId = ent.getFirstThreadId();// 如果線程ID有效,執行續期操作if (threadId != null) {// 異步執行鎖的續期操作CompletionStage<Boolean> future = RedissonBaseLock.this.renewExpirationAsync(threadId);// 處理續期結果future.whenComplete((res, e) -> {// 如果續期過程中發生異常if (e != null) {// 記錄錯誤日志RedissonBaseLock.log.error("Can't update lock " + RedissonBaseLock.this.getRawName() + " expiration", e);// 從續期映射表中移除條目,停止續期RedissonBaseLock.EXPIRATION_RENEWAL_MAP.remove(RedissonBaseLock.this.getEntryName());} else {// 續期操作成功完成if (res) {// 如果續期成功(返回true),遞歸調用繼續下一輪續期// 這樣就形成了持續的自動續期循環RedissonBaseLock.this.renewExpiration();} else {// 如果續期失敗(返回false),說明鎖已經不存在或不屬于當前線程// 取消續期任務,清理資源RedissonBaseLock.this.cancelExpirationRenewal((Long)null);}}});}}}}, this.internalLockLeaseTime / 3L, TimeUnit.MILLISECONDS); // 在租約時間的1/3處執行續期// 將定時任務保存到條目中,用于后續的取消操作ee.setTimeout(task);}
}

最后完美結束對應的獲取鎖的過程,返回一個對應的時間值 ttl
在這里插入圖片描述
如果返回的是null代表加鎖成功,否則是加鎖失敗,此時會進行訂閱持有鎖者this.subscribe(threadId),如果釋放鎖會通知這個獲取鎖失敗的線程,會將這個線程喚醒。

四、解鎖流程解析

解鎖的流程

解鎖時同樣使用 Lua 腳本,保證原子性:

if (redis.call('hexists', KEYS[1], ARGV[2]) == 0) thenreturn nil;
end;local counter = redis.call('hincrby', KEYS[1], ARGV[2], -1);if (counter > 0) thenreturn 0;
elseredis.call('del', KEYS[1]);return 1;
end;

解釋:

  1. 檢查當前線程是否持有鎖。
  2. 如果是可重入鎖,計數 -1。
  3. 如果計數為 0,則刪除鎖。

六、源碼設計亮點

  1. Lua 腳本保證原子性,避免分布式并發問題。
  2. 可重入性設計:使用 hash 結構存儲線程標識和重入次數。
  3. 鎖超時釋放設計:避免死鎖問題。
  4. 看門狗機制:保證長時間任務也能安全持有鎖。
  5. 異步化設計:Redisson 提供 lockAsync() 等方法,方便高并發場景。

七、總結

  • Redisson 的分布式鎖實現基于 Redis + Lua 腳本,解決了互斥、可重入和死鎖問題。
  • 看門狗續期機制 是 Redisson 的亮點,保證了業務執行時間不可預測的情況下的安全性。
  • 在生產環境中,Redisson 的分布式鎖相較于 SETNX + EXPIRE 的手寫版本,更加健壯和可靠。

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

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

相關文章

uni-app支持單多選、搜索、查詢、限制能否點擊組件

<template><view class="multi-select-container" :class="{ single-select: !multiple, no-search: !searchable }"><!-- 當組件被禁用時,直接顯示選中的內容 --><view class="disabled-display" v-if="disabled &a…

TFT屏幕:STM32硬件SPI+DMA+隊列自動傳輸

看了網上的很多的SPIDMA的代碼&#xff0c;感覺都有一些缺陷&#xff0c;就是基本都是需要有手動等待DMA完成的這個操作&#xff0c;我感覺這種等待操作在很大程度上浪費了時間&#xff0c;那么我加入的“隊列”就是一種將等待時間利用起來的方法。原本的SPIDMA的操作邏輯如下圖…

AI操作系統語言模型設計 之1 基于意識的Face-Gate-Window的共軛路徑的思維-認知-情感嵌套模型

摘要&#xff08;AI生成&#xff09;本文提出了一種創新的AI操作系統語言模型設計框架&#xff0c;將人類意識活動的分層結構映射到人工智能系統中。該模型包含三個嵌套層次&#xff1a;理性思維層&#xff08;Face層&#xff09;&#xff1a;采用雙面膠隱喻&#xff08;A/B面&…

瘋狂星期四文案網第57天運營日記

網站運營第57天&#xff0c;點擊觀站&#xff1a; 瘋狂星期四 crazy-thursday.com 全網最全的瘋狂星期四文案網站 運營報告 今日訪問量 今日搜索引擎收錄情況

SQLark:一款面向信創應用開發者的數據庫開發和管理工具

SQLark 是一款面向信創應用開發者的數據庫開發和管理工具&#xff0c;用于快速查詢、創建和管理不同類型的數據庫系統&#xff0c;現已支持達夢、Oracle、MySQL、PostgreSQL 數據庫。 SQLark 提供了對多種數據庫的連接支持&#xff0c;實現跨平臺數據庫管理的無縫切換&#xff…

BigDecimal——解決Java浮點數值精度問題:快速入門與使用

在Java開發中&#xff0c;涉及金額計算、科學計數或需要高精度數值處理時&#xff0c;你是否遇到過這樣的困惑&#xff1f;用double計算0.1加0.2&#xff0c;結果竟不是0.3&#xff1b;用float存儲商品價格&#xff0c;小數點后兩位莫名多出幾位亂碼&#xff1b;甚至在金融系統…

wpf之WrapPanel

前言 WrapPanel類似winform中的FlowLayoutPanel&#xff0c;采用流式布局。 1、Orientation 該屬性指定WrapPanel中子空間布局的方向&#xff0c;有水平和垂直方向兩種 1&#xff09;Horizontal 水平方向 子元素Button按照水平方向排列&#xff0c;如果一行排滿了自動換下一…

Woody:開源Java應用性能診斷分析工具

核心價值 Woody是一款專注于Java應用性能問題診斷的工具&#xff0c;旨在幫助開發者 定位高GC頻率問題&#xff0c;識別內存分配熱點分析CPU使用率過高的代碼路徑追蹤接口耗時瓶頸&#xff0c;定位內部操作耗時占比診斷鎖競爭問題&#xff0c;支持精準優化針對特定業務接口/請…

《山東棒球》板球比賽規則·棒球1號位

? Baseball vs Cricket 終極科普&#xff5c;規則異同發展史全解&#xff01;Hey sports babes&#xff01;別再傻傻分不清棒球?和板球&#xff01;全網最清晰雙運動對照指南來啦&#xff5e;? 棒球 Baseball&#xff5c;美式激情風暴Core Goal核心目標擊球員&#xff08;Ba…

【游戲開發】Houdini相較于Blender在游戲開發上有什么優劣勢?我該怎么選擇開發工具?

在游戲開發中&#xff0c;Houdini與Blender的選擇需結合項目規模、技術需求和團隊資源綜合考量。以下是兩者的核心優劣勢對比及決策建議&#xff1a; 一、核心優劣勢對比 Houdini的優勢與局限 優勢&#xff1a;程序化內容生成的統治力 Houdini的節點系統&#xff08;如VEX語言、…

基于開源AI智能名片鏈動2+1模式S2B2C商城小程序的用戶活躍度提升與價值挖掘策略研究

摘要&#xff1a;本文聚焦于在開源AI智能名片鏈動21模式S2B2C商城小程序環境下&#xff0c;探討如何提高用戶活躍度并挖掘用戶價值。在用戶留存的基礎上&#xff0c;通過分析該特定模式與小程序的特點&#xff0c;提出一系列針對性的策略&#xff0c;旨在借助開源AI智能名片以及…

《投資-41》- 自然=》生物=》人類社會=》商業=》金融=》股市=》投資,其層層疊加構建中內在的相似的規律和規則

從自然到投資的層層遞進中&#xff0c;盡管各領域看似差異巨大&#xff0c;但內在遵循著相似的規律和規則。這些規律體現了“底層邏輯的普適性”&#xff0c;即不同系統在動態平衡、資源分配、信息傳遞和反饋調節等方面具有共性。以下是關鍵規律的解析&#xff1a;1. 能量流動與…

VSCode中調試python腳本

VSCode中安裝以下插件 ms-python.python&#xff1a;python調試ms-python.vscode-pylance&#xff1a;代碼跳轉&#xff08;非必要&#xff09; 配置launch.json 在當前工作區&#xff0c;按此路徑.vscode\launch.json新建launch.json文件&#xff0c;并配置以下參數&#x…

動作指令活體檢測通過動態交互驗證真實活人,保障安全

在當今社會&#xff0c;人臉識別技術已深入日常生活的方方面面&#xff0c;從手機解鎖、移動支付到遠程開戶、門禁考勤&#xff0c;人臉識別技術已無處不在。然而&#xff0c;這項技術也面臨著嚴峻的安全挑戰&#xff1a;打印照片、播放視頻、制作3D面具等簡單的“欺騙手段”都…

KingbaseES數據庫:開發基礎教程,從部署到安全的全方位實踐

KingbaseES數據庫&#xff1a;開發基礎教程&#xff0c;從部署到安全的全方位實踐 KingbaseES數據庫&#xff1a;開發基礎教程&#xff0c;從部署到安全的全方位實踐&#xff0c;本文圍繞 KingbaseES 數據庫開發核心基礎展開。先介紹三種部署模式&#xff0c;即單機、雙機熱備、…

安裝nodejs安裝node.js安裝教程(Windows Linux)

文章目錄Linux**一、下載 Node.js**1. **訪問官網**&#xff1a;2. **選擇版本**&#xff1a;**二、安裝 Node.js****方法 1&#xff1a;使用包管理器&#xff08;推薦&#xff09;****Ubuntu/Debian 系統**1. **更新包列表**&#xff1a;2. **安裝 Node.js**&#xff1a;3. **…

shell腳本函數介紹

1. 函數 (Functions)定義與優勢函數是可重復使用的功能模塊優勢&#xff1a;代碼復用&#xff0c;直接調用解決問題分類內置函數&#xff1a;編程語言自帶的函數&#xff08;如 print&#xff09;自定義函數&#xff1a;程序員自己編寫的函數定義語法# 方式一 function 函數名(…

DAY 20 奇異值SVD分解-2025.9.1

奇異值SVD分解 知識點回顧&#xff1a; 線性代數概念回顧奇異值推導奇異值的應用 a. 特征降維&#xff1a;對高維數據減小計算量、可視化 b. 數據重構&#xff1a;比如重構信號、重構圖像&#xff08;可以實現有損壓縮&#xff0c;k 越小壓縮率越高&#xff0c;但圖像質量損失…

《C++——定長內存池》

一、為什么需要內存池&#xff1f; 常規的new/delete操作存在兩個主要問題&#xff1a; 性能開銷大&#xff1a;每次new都需要向操作系統申請內存&#xff0c;delete需要歸還給系統&#xff0c;這涉及內核態與用戶態的切換&#xff0c;在高頻次調用時性能損耗明顯。 內存碎片&a…

【跨境電商】上中下游解釋,以寵物行業為例

上中下游概念及其在寵物行業的應用 在產業鏈分析中&#xff0c;“上中下游”指的是一個產品或服務的不同環節&#xff1a;上游涉及原材料供應和基礎資源&#xff0c;中游負責生產加工和制造&#xff0c;下游則包括銷售、分銷和服務。這種劃分有助于理解整個價值鏈的運作。下面&…