Spring循環依賴以及三個級別緩存

Spring循環依賴以及三個級別緩存

什么是循環依賴?

循環依賴,顧名思義,就是指兩個或多個 Spring Bean 之間相互依賴,形成一個閉環。

image-20250725163349432

最常見也是 Spring 能夠“解決”的循環依賴是構造器注入setter 注入 混合或單獨使用時,發生在 單例(Singleton) Bean 上的情況。


什么是三級緩存?

image-20250725165502311

第一級緩存:singletonObjects (一級緩存 / 成品 Bean 緩存)

  • 類型: ConcurrentHashMap<String, Object>

  • 作用: 存放已經完全初始化好,并且可供使用的單例 Bean。當一個 Bean 在這里被找到時,它就是“成品”了,可以直接返回給請求者。

第二級緩存:earlySingletonObjects (二級緩存 / 早期暴露對象緩存)

  • 類型: ConcurrentHashMap<String, Object>

  • 作用: 存放已經實例化但尚未完成屬性填充和初始化的 Bean。這些 Bean 是“半成品”,但它們被提前暴露出來,以便解決循環依賴。

第三級緩存:singletonFactories (三級緩存 / 早期單例工廠緩存)

  • 類型: HashMap<String, ObjectFactory<?>>
  • 作用: 存放創建 Bean 的 ObjectFactory。這個工廠負責生產“半成品”的 Bean 實例(可能經過 AOP 代理)。它是解決循環依賴的關鍵所在,尤其是在涉及到 AOP 代理的場景。
  • 特點: 這里的不是 Bean 實例本身,而是一個能夠獲取早期 Bean 引用(可能是原始對象,也可能是代理對象)的工廠

我們可以看到三級緩存singletonFactories的類型是HashMap,并且map的value值為ObjectFactory,不同于其他兩級緩存。

💠為什么 valueObjectFactory 而不是 Object

  • 這就是第三級緩存的精妙之處,也是它能夠解決循環依賴與 AOP 代理同時存在問題的關鍵:

    1. 延遲生成早期引用:
      • 在 Bean A 實例化后,它會立即將一個 ObjectFactory 放入第三級緩存。
      • 這個工廠只有在另一個 Bean B 發生循環依賴,并且需要提前獲取 Bean A 的引用時,才會被調用(singletonFactory.getObject())。
      • 這種延遲機制使得 Spring 可以在真正需要 Bean A 的早期引用時,才決定并生成它。
    2. 處理 AOP 代理:
      • 如果 Bean A 需要進行 AOP 代理(例如,因為它上面有 @Transactional 注解,或者被某個切面匹配到),那么在 ObjectFactorygetObject() 方法被調用時,Spring 的 AOP 邏輯會被觸發。
      • 此時,getObject() 方法將不會簡單地返回 Bean A 的原始實例,而是會返回 Bean A 的代理實例
      • 這個代理實例隨后會被放入第二級緩存 earlySingletonObjects,供依賴方使用。

    總結來說:

    • 一級和二級緩存直接存放Bean 實例(成品或半成品)。
    • 第三級緩存存放的是一個**“生產 Bean 實例的工廠”**。這個工廠在被調用時,能根據 Bean 的特性(特別是是否需要 AOP 代理),決定是返回原始實例還是其代理實例。

    正是因為 singletonFactories 存儲的是一個能夠“生產”早期 Bean 實例的工廠,而不是直接的 Bean 實例,Spring 才能夠在循環依賴的場景下,靈活地提供經過 AOP 代理的早期 Bean 引用,從而保證了 Bean 引用的一致性,解決了復雜場景下的循環依賴問題。

💠為什么 singletonFactories 使用 HashMap?

  • singletonObjectsearlySingletonObjects 需要 ConcurrentHashMap 是因為它們是并發訪問的熱點,需要內部的并發控制來保證性能和線程安全。

  • singletonFactories 使用 HashMap 是因為它的所有相關操作都已經被外部的 synchronized (this.singletonObjects) 鎖保護起來,本身不需要內部的并發機制。 在這個全局鎖的保護下,使用 HashMap 既滿足了線程安全,又因為其操作頻率相對較低而沒有性能瓶頸。


循環依賴流程?

image-20250725173258369

這五個是單例 Bean 的創建和緩存緊密相關的核心環節,清晰描述了 Spring 單例 Bean 的生命周期

  1. 緩存查詢

? 這是獲取 Bean 的第一步,也是最快、最高效的方式。

  • 目標:檢查 Spring IoC 容器中是否已經存在一個完全初始化好的 Bean 實例。

  • 實現:Spring 會首先去它的**一級緩存(singletonObjects)**中查找。

  • 結果

    • 如果找到了,直接返回這個 Bean 實例。這是最理想的情況,省去了后續所有創建和初始化步驟。

    • 如果沒找到,并且這個 Bean 當前正在創建中(表示可能存在循環依賴),它會進一步嘗試從**二級緩存(earlySingletonObjects三級緩存(singletonFactories)**獲取一個早期引用。

  1. 創建對象 (實例化)

? 如果緩存中沒有找到可用的 Bean,那么 Spring 就會開始創建新的 Bean 實例。

  • 目標:根據 Bean 定義(BeanDefinition),通過反射等方式,生成 Bean 的原始實例。
  • 實現
    • 對于普通 Bean,通常是調用其構造函數來創建對象。
    • 在實例化完成后,Bean 的原始實例就存在了,但此時它只是一個“空殼”,沒有任何屬性被填充,也沒有進行任何初始化操作。
  • 關鍵時機:這個階段是 Bean 生命周期中首次出現具體對象的地方。也是在這個階段之后,如果存在循環依賴且允許早期引用,Spring 會將 Bean 的一個早期引用工廠ObjectFactory)放入三級緩存
  1. 填充屬性

? 對象創建后,它需要被注入所依賴的其他 Bean 和配置。

  • 目標:將 Bean 定義中聲明的屬性(通過 @Autowired@Resource 等注解或 XML 配置)注入到剛創建的 Bean 實例中。
  • 實現
    • Spring IoC 容器會解析 Bean 的依賴關系。
    • 如果是通過 Setter 方法注入字段注入,Spring 會查找對應的依賴 Bean。
    • 如果在這個過程中遇到循環依賴(例如,BeanA 依賴 BeanB,而 BeanB 此時需要 BeanA),Spring 會利用之前放入三級緩存中的 ObjectFactory 來獲取 BeanA 的早期引用(可能是代理對象),從而打破循環。
  • 關鍵時機:循環依賴問題主要發生在這個階段。
  1. 初始化

? 屬性填充完成后,Bean 實例就具備了它所有的依賴,但可能還需要進行一些自定義的初始化工作。

  • 目標:執行 Bean 的自定義初始化邏輯和生命周期回調。

  • 實現

    • Aware 接口回調:如果 Bean 實現了 BeanNameAwareBeanFactoryAwareApplicationContextAware 等接口,Spring 會調用相應的方法注入名稱、工廠或上下文。
    • BeanPostProcessor 前置處理:調用所有注冊的 BeanPostProcessorpostProcessBeforeInitialization 方法。
    • @PostConstruct 方法:執行 Bean 中被 @PostConstruct 注解標記的方法。
    • InitializingBean 接口 afterPropertiesSet() 方法:如果 Bean 實現了 InitializingBean 接口,調用其 afterPropertiesSet() 方法。
    • 自定義 init-method:執行 Bean 定義中指定的自定義初始化方法(如 XML 配置中的 init-method)。
    • BeanPostProcessor 后置處理:調用所有注冊的 BeanPostProcessorpostProcessAfterInitialization 方法。AOP 代理通常在這個階段發生postProcessAfterInitialization 方法會返回 Bean 的代理對象。
  • 關鍵時機:Bean 的最終形態(包含所有代理)通常在這個階段確定。

  1. 緩存轉移 (放入一級緩存)

? 所有步驟都完成后,這個 Bean 就“大功告成”了。

  • 目標:將完全初始化并可用的 Bean 實例,從所有臨時緩存中移除,并放入最終的成品緩存。
  • 實現
    • Spring 會將這個 Bean 實例放入一級緩存(singletonObjects
    • 同時,從**二級緩存(earlySingletonObjects三級緩存(singletonFactories)**中移除該 Bean 的相關條目,因為它現在已經是“成品”了,不再是早期引用或工廠。
  • 結果:此后,任何對該 Bean 的請求都將直接從一級緩存中獲取,高效且快速。

來看BeanA和BeanB這個循環依賴流程,在三級緩存中是怎么作用的?

流程參考視頻[徹底拿捏Spring循環依賴以及三個級別緩存嗶哩嗶哩bilibili]

首先來看BeanA的創建過程

?1.緩存查詢

查詢三級緩存,都沒有,回到主流程,進行創建對象。

image-20250725211931540

?2.創建對象

我們通過反射創建對象,包裝成ObjectFactory類型,放到三級緩存singletonFactories中,再回到主流程,進行填充屬性。

image-20250725212203302

?3.填充屬性

因為填充的是BeanB,所以要開始創建BeanB,同樣走的是BeanA走的那一套流程 。

? 🎯3.1 進行緩存查詢,查詢不到,進行下一步創建對象。

image-20250725211931540

? 🎯3.2 進行通過反射創建對象,此時三級緩存singletonFactories中已經有BeanA了,將BeanB放進去

image-20250725212940589

? 🎯3.3填充屬性,要填充BeanA,又要開始創建BeanA的那一套流程。

? 3.3.1 進行緩存查詢,此時三級緩存中是有的,執行一些額外操作

image-20250725213345428

? 來看源碼,重點看2.3-2.6 ,通過getObject獲得對象,放到二級緩存中,并在三級緩存中移除它。

? 這樣BeanB的填充屬性就完成了。回到BeanB的主流程中,進行初始化

protected Object getSingleton(String beanName, boolean allowEarlyReference) {// 1. 首先嘗試從一級緩存中獲取成品 BeanObject singletonObject = this.singletonObjects.get(beanName);// 2. 如果一級緩存中沒有,并且該 Bean 正在創建中 (解決循環依賴的關鍵入口)if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {// 2.1. 嘗試從二級緩存中獲取早期暴露的 BeansingletonObject = this.earlySingletonObjects.get(beanName);// 2.2. 如果二級緩存中也沒有,并且允許早期引用 (allowEarlyReference通常為true,表示允許循環依賴)if (singletonObject == null && allowEarlyReference) {// 對 singletonObjects 進行同步鎖,保證線程安全,防止多個線程同時處理同一個Bean的早期引用synchronized(this.singletonObjects) {// 再次檢查一級緩存,因為在同步塊外可能被其他線程放入singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null) { // 再次確認一級緩存沒有// 再次檢查二級緩存,確保同步塊內沒有被其他線程放入singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null) { // 再次確認二級緩存沒有// 2.3. 從三級緩存中獲取 ObjectFactoryObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);if (singletonFactory != null) {// 2.4. 調用 ObjectFactory 的 getObject() 方法獲取早期 Bean 實例//      這可能是原始對象,也可能是代理對象(如果AOP觸發)singletonObject = singletonFactory.getObject();// 2.5. 將獲取到的早期 Bean 實例放入二級緩存this.earlySingletonObjects.put(beanName, singletonObject);// 2.6. 從三級緩存中移除對應的 ObjectFactory,因為已經使用了this.singletonFactories.remove(beanName);}}}}}}return singletonObject;
}

? 🎯3.4 初始化,執行 Bean 的自定義初始化邏輯和生命周期回調。

? 🎯3.5 緩存轉移,因為BeanB已經完全創建完畢了,所有要將緩存里面的對象進行轉移

image-20250725214714531

? 看源碼,將BeanB放入一級緩存中,從三級緩存中移除,從二級緩存中移除。

protected void addSingleton(String beanName, Object singletonObject) {synchronized(this.singletonObjects) {this.singletonObjects.put(beanName, singletonObject); // 放入一級緩存this.singletonFactories.remove(beanName); // 從三級緩存移除,因為已經不是早期引用了this.earlySingletonObjects.remove(beanName); // 從二級緩存移除,因為已經不是早期引用了this.registeredSingletons.add(beanName); // 記錄為已注冊的單例}
}
image-20250725215830859

? 到這里整個BeanB的創建過程就完成了,但這只是BeanA填充屬性,我們還要跳回BeanA的創建過程。

image-20250725220101752

?4.初始化

?5.緩存轉移

同上類似,移除二級和三級緩存中的BeanA,添加一級緩存中

image-20250725220224994 image-20250725220414321

為什么需要第三級緩存,有兩個不就行了

簡單來說,第三級緩存是為了解決當 Bean 存在循環依賴,并且還需要進行 AOP 代理(或者其他后置處理器處理)時的問題。

如果沒有第三級緩存,僅靠兩級緩存,Spring 無法在需要 AOP 代理時正確處理循環依賴。

為什么兩級緩存不夠?

我們來想象一下只有兩級緩存(singletonObjectsearlySingletonObjects)的場景:

假設有兩個 Bean:BeanABeanB

  • BeanA 依賴 BeanB
  • BeanB 依賴 BeanA
  • 最重要的是:BeanA 需要被 AOP 代理(例如,它上面有 @Transactional 注解,或者自定義的切面)。

流程推演(只有兩級緩存):

  1. 創建 BeanA
    • Spring 開始創建 BeanA
    • 實例化 BeanA 的原始對象 a
    • a 放入二級緩存 earlySingletonObjects
  2. BeanA 填充屬性,需要 BeanB
    • BeanA 的屬性填充過程中,發現需要 BeanB
    • Spring 暫停 BeanA 的創建,轉而創建 BeanB
  3. 創建 BeanB
    • Spring 開始創建 BeanB
    • 實例化 BeanB 的原始對象 b
    • b 放入二級緩存 earlySingletonObjects
  4. BeanB 填充屬性,需要 BeanA
    • BeanB 的屬性填充過程中,發現需要 BeanA
    • Spring 到緩存中查找 BeanA
      • 在一級緩存 singletonObjects 中找不到 BeanA(因為它還沒初始化完)。
      • 二級緩存 earlySingletonObjects 中找到了 BeanA 的原始對象 a
      • a 注射到 BeanB 中。
  5. BeanB 完成初始化:
    • BeanB 完成屬性填充和所有初始化步驟。
    • BeanB 被放入一級緩存 singletonObjects,完成創建。
  6. BeanA 繼續完成初始化:
    • BeanA 現在得到了完整的 BeanB
    • BeanA 繼續進行初始化步驟,包括執行 AOP 代理邏輯
    • 此時,問題來了:當 BeanA 執行 AOP 代理時,它會生成一個代理對象 a_proxy。這個 a_proxy 才是最終應該被其他 Bean 引用和使用的對象。
    • 但問題是,BeanB 在第4步中已經獲取并使用了 BeanA原始對象 a,而不是 a_proxy

結論: 在這種情況下,BeanB 引用的是未被 AOP 代理的 BeanA 原始對象,而其他后來的 Bean 引用的是 BeanA 的代理對象。這就導致了 Bean 實例的不一致性,后續對 BeanA 的方法調用可能無法觸發 AOP 邏輯,從而導致功能異常。

第三級緩存 singletonFactories 的作用

第三級緩存 singletonFactories 存放的不是 Bean 實例,而是一個 ObjectFactory(一個工廠)。這個工廠的 getObject() 方法,會在需要時,動態地生成并返回 Bean 的早期引用

關鍵在于:這個工廠在生成早期引用時,會判斷當前 Bean 是否需要進行 AOP 代理。如果需要,它會直接返回代理對象;如果不需要,它就返回原始對象。

引入第三級緩存后的流程推演:

  1. 創建 BeanA
    • Spring 開始創建 BeanA
    • 實例化 BeanA 的原始對象 a
    • Spring 將一個 ObjectFactory 放入第三級緩存 singletonFactories 這個工廠在被調用時,會負責生成 BeanA 的早期引用(可能是原始對象或代理對象)。
    • 同時,將 BeanAsingletonFactories 中移除,將原始對象 a 放入 earlySingletonObjects (二級緩存)
  2. BeanA 填充屬性,需要 BeanB
    • BeanA 的屬性填充過程中,發現需要 BeanB
    • Spring 暫停 BeanA 的創建,轉而創建 BeanB
  3. 創建 BeanB(步驟同前,不再贅述)。
  4. BeanB 填充屬性,需要 BeanA
    • BeanB 的屬性填充過程中,發現需要 BeanA
    • Spring 到緩存中查找 BeanA
      • 在一級緩存 singletonObjects 中找不到。
      • 在二級緩存 earlySingletonObjects 中找到了 BeanA 的原始對象 a
      • 如果此時 BeanA 需要被 AOP 代理,并且 AOP 后置處理器已經準備好,那么 earlySingletonObjects 中的 a 將不會直接返回給 BeanB
      • 而是會通過 singletonFactories 中對應的 ObjectFactory 來獲取 BeanA 的早期引用。 這個工廠被調用時,會觸發 BeanA 的 AOP 代理邏輯,生成 a_proxy
      • a_proxy 會被放入二級緩存 earlySingletonObjects,替換掉原始對象 a(重要:這里原始對象被替換為代理對象)
      • 然后,a_proxy 被注射到 BeanB 中。
  5. BeanB 完成初始化:
    • BeanB 完成屬性填充和所有初始化步驟,并被放入一級緩存 singletonObjects
  6. BeanA 繼續完成初始化:
    • BeanA 現在得到了完整的 BeanB
    • BeanA 繼續進行剩余的初始化步驟。
    • BeanA 的最終代理對象(如果之前生成過,就是那個 a_proxy;如果沒有,則在此處生成并最終確定)被放入一級緩存 singletonObjects

核心優勢:

通過三級緩存,當一個 Bean(比如 BeanA)在創建過程中被另一個 Bean(比如 BeanB)提前引用時,并且這個 BeanA 需要被 AOP 代理,三級緩存中的 ObjectFactory 能夠保證所有獲取到的早期引用都是經過 AOP 代理后的對象。這樣就確保了 Bean 實例的一致性,無論是在循環依賴中被提前引用,還是在后續正常流程中被引用,都將得到的是同一個 AOP 代理后的實例。

總結: 兩級緩存無法處理 AOP 代理的場景,因為在 Bean 完全初始化前無法確定最終是返回原始對象還是代理對象。第三級緩存通過存儲一個工廠(ObjectFactory),使得 Spring 能夠在需要時才決定并生成最終的早期引用(可以是原始對象,也可以是代理對象),從而保證了在循環依賴和 AOP 代理同時存在時的正確性和一致性。

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

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

相關文章

《零基礎入門AI:OpenCV圖像預處理進一步學習》

本文全面講解OpenCV圖像預處理的七大核心技術&#xff08;插值方法、邊緣填充、圖像矯正&#xff08;透視變換&#xff09;、圖像掩膜、ROI切割、圖像添加水印、圖像噪點消除&#xff09;&#xff0c;每個知識點都配有詳細解釋和實用代碼示例&#xff0c;幫助初學者建立系統的圖…

MongoDB的內存和核心數對于運行效率的影響

在 MongoDB 線上生產環境中&#xff0c;CPU&#xff08;核心&#xff09; 和 內存 是兩大關鍵硬件資源&#xff0c;它們在不同的操作場景下發揮著核心作用&#xff0c;共同影響著數據庫的性能、穩定性和擴展性。理解它們的作用場景至關重要&#xff0c;是容量規劃、性能優化和故…

自己的SAPGUI嘗試

為滿足用戶需求&#xff0c;博主做了一個臺賬管理程序&#xff0c;嘗試用自己的程序做GUI&#xff0c;用SAP 系統做數據庫。 運行了半年&#xff0c;程序很nice,用戶每天都在高效的使用&#xff0c;已經有十幾萬的數據。 總結一下這次自己的GUI嘗試&#xff0c;好處是C# WINFOR…

高效處理 JSON 數據:JsonUtil 工具類全方位解析與實戰

在現代軟件開發中,JSON(JavaScript Object Notation)已成為數據交換的“通用語言”——從前后端接口通信到微服務數據交互,從配置文件解析到日志格式化,幾乎所有場景都離不開JSON的處理。然而,原生JSON框架(如FastJSON、Jackson)的API往往需要大量重復代碼,且空指針、…

Python 庫手冊:xmlrpc.client 與 xmlrpc.server 模塊

xmlrpc.client 和 xmlrpc.server 是 Python 標準庫中用于構建基于 XML-RPC 協議的遠程過程調用&#xff08;RPC&#xff09;通信模塊。xmlrpc.client 用于編寫客戶端程序&#xff0c;向遠程服務器發起方法調用。xmlrpc.server 用于編寫服務器端&#xff0c;暴露本地方法供遠程客…

渲染篇(一):從零實現一個“微型React”:Virtual DOM的真面目

渲染篇(一)&#xff1a;從零實現一個“微型React”&#xff1a;Virtual DOM的真面目 引子&#xff1a;前端性能的“永恒之問” 在前面兩章中&#xff0c;我們已經奠定了堅實的架構基礎。我們用“任務調度器”建立了聲明式和模塊化的編程范式&#xff0c;并通過對比MVC等模式論…

SWC 深入全面講解

一、核心功能與原理 1. 高性能編譯 Rust 架構優勢&#xff1a;SWC 基于 Rust 編寫&#xff0c;利用 Rust 的性能和并發性優勢&#xff0c;編譯速度比 Babel 快約 20 倍&#xff0c;比 TypeScript 編譯器更快。并行編譯&#xff1a;支持多線程并行處理&#xff0c;在四核基準測試…

XML Expat Parser:深入解析與高效應用

XML Expat Parser:深入解析與高效應用 引言 XML(可擴展標記語言)作為一種廣泛使用的標記語言,在數據交換、存儲和表示中扮演著重要角色。XML Expat Parser 是一個高性能、可擴展的XML解析庫,廣泛應用于各種編程語言中。本文將深入探討XML Expat Parser 的原理、特性以及…

【Python】自動化GIT提交

在日常開發中&#xff0c;我們經常需要頻繁地向 Git 倉庫提交代碼。雖然 git add、git commit、git push 這幾個命令并不復雜&#xff0c;但重復操作容易出錯&#xff0c;也浪費時間。本文將介紹如何使用 Python 腳本自動化完成 Git 提交流程&#xff0c;讓開發更高效&#xff…

基于Qlearning強化學習的水下無人航行器路徑規劃與避障系統matlab性能仿真

目錄 1.引言 2.算法仿真效果演示 3.數據集格式或算法參數簡介 4.算法涉及理論知識概要 5.參考文獻 6.完整算法代碼文件獲得 1.引言 水下無人航行器 (Autonomous Underwater Vehicle, AUV) 的路徑規劃與避障是海洋探索、資源開發和軍事應用中的關鍵技術。傳統的路徑規劃方…

模塊自由拼裝!Python重構DSSAT作物模塊教程(以雜交水稻為例)

基于過程的作物生長模型&#xff08;Process-based Crop Growth Simulation Model&#xff09;在模擬作物對氣候變化的響應與適應、農田管理優化、作物品種和株型篩選、農業碳中和、農田固碳減排等領域扮演著越來越重要的作用。Decision Support Systems for Agrotechnology Tr…

Java項目接口權限校驗的靈活實現

引言 在Java Web開發中&#xff0c;接口權限校驗是保護系統資源安全的關鍵機制。本文將介紹一種靈活、可配置的接口權限校驗方案&#xff0c;通過注解驅動和攔截器實現&#xff0c;既能保證安全性&#xff0c;又能靈活控制哪些接口需要校驗。 設計思路 實現方案的核心設計要點&…

瀚高DB兼容MySQL if函數

文章目錄環境癥狀問題原因解決方案環境 系統平臺&#xff1a;Linux x86-64 Red Hat Enterprise Linux 7 版本&#xff1a;4.5 癥狀 MySQL if函數在瀚高DB當中沒有&#xff0c;源應用在用到if函數時&#xff0c;就會報if函數不存在的錯誤信息。為此&#xff0c;我們需要根據業…

基于深度學習的胸部 X 光圖像肺炎分類系統(六)

目錄 結果指標解讀 一、為什么選擇這些指標&#xff1f; 二、各指標的定義和解讀 1. 準確率&#xff08;Accuracy&#xff09; 2. 損失&#xff08;Loss&#xff09; 3. 精確率&#xff08;Precision&#xff09; 4. 召回率&#xff08;Recall&#xff09; 三、這些指標…

區塊鏈性能優化策略:從理論到實踐

目錄 區塊鏈性能優化策略:從理論到實踐 1. 引言:區塊鏈性能的挑戰 2. 性能評估指標 2.1 核心性能指標 2.2 性能瓶頸分析 3. 分層優化策略 3.1 網絡層優化 3.1.1 Gossip協議改進 3.1.2 網絡分片 3.2 共識層優化 3.2.1 PBFT優化 3.3 數據層優化 3.3.1 狀態樹優化 3.3.2 區塊數據…

【VLLM】open-webui部署模型全流程

目錄 前言 一、租用服務器到服務器連接VScode全流程(可選) 二、下載模型到本地服務器 2.1 進入魔塔社區官網 2.2 選擇下載模型 2.3 執行下載 三、部署VLLM 3.1 參考vllm官網文檔 3.2 查看硬件要求 3.3 安裝vLLM框架 3.4 啟動模型服務 方法1:直接啟動下載的本地模…

辦公自動化入門:如何高效將圖片整合為PDF文檔

將多張圖片合并到一個PDF文件中可以幫助保持特定的順序和布局&#xff0c;同時確保圖像的質量不會因為格式轉換而下降。它是免費&#xff0c;不限次數&#xff0c;批量導入也毫無壓力。操作堪比發朋友圈&#xff1a;拖圖進來 → 選個紙張尺寸 → 點擊轉換 → 指定保存路徑&…

使用寶塔面板搭建 PHP 環境開發一個簡單的 PHP 例子

目錄一、引言二、準備工作2.1 服務器選擇2.2 下載安裝寶塔面板三、使用寶塔面板搭建 PHP 環境3.1 登錄寶塔面板3.2 選擇 Web Server3.3 安裝 PHP3.4 安裝 MySQL 數據庫四、開發一個簡單的 PHP 例子4.1 創建 PHP 文件4.2 編寫 PHP 代碼4.3 設置站點4.4 訪問 PHP 頁面五、常見問題…

AWS WebRTC:我們的業務模式

拉流、卡錄基本流程 設備端&#xff08;攝像機&#xff09; 與 App端 是通過 AWS KVS WebRTC 信令服務進行“點對點連接”的&#xff0c;真正的媒體數據&#xff08;音視頻&#xff09;是通過 WebRTC 的 ICE 通道&#xff08;P2P 或 TURN&#xff09;直接傳輸的&#xff0c;而不…

使用Python,OpenCV,K-Means聚類查找圖像中最主要的顏色

使用Python&#xff0c;OpenCV&#xff0c;K-Means聚類查找圖像中最主要的顏色 分別把跑圖聚類選取1, 2, 3&#xff0c;4, 5, 6, 7&#xff0c;8, 9種主要顏色并繪制colormap顏色圖; 效果圖 分別把跑圖聚類選取3&#xff0c;4, 5&#xff0c;7&#xff0c;9種主要顏色并繪制…