黑馬點評項目難點-動態代理,sychronized,@Transactional失效的情況

文章目錄

  • 難點1:synchronize
    • synchronized 的底層實現
    • 鎖的具體操作
    • 舉例說明
      • 結論
  • 難點2:動態代理和@Transactional失效問題
      • `@Transactional` 工作原理
      • 關鍵點
      • 示例分析
      • 正確的使用方式
      • 結論
      • 建議

難點所在代碼塊

    @Overridepublic Result seckillVoucher(Long voucherId) {
//        1.查詢優惠券SeckillVoucher voucher = seckillVoucherService.getById(voucherId);//        2.判斷秒殺是否開始if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {return fail("秒殺尚未開始");}
//        3.判斷秒殺是否結束if (voucher.getEndTime().isBefore(LocalDateTime.now())) {return fail("秒殺已經結束");}
//        4.判斷庫存是否充足if (voucher.getStock()<1) {return fail("庫存不足");}Long userId = UserHolder.getUser().getId();synchronized (userId.toString().intern()){//避免線程安全問題,確保每次5返回的都是"5",而不是不一樣的5.toString()//如果直接return createVoucherOrder(voucherId);拿到的是當前VoucherOrderServiceImpl對象,而不是他的代理對象//注意seckillVoucher方法沒有加@Transactional,會導致如果創建訂單失敗,則不會回滾,所以需要加@Transactional//事務要想生效,是Spring對VoucherOrderServiceImpl做了動態代理,拿到了他的代理對象,用createVoucherOrder(voucherId);做的事務處理//直接return createVoucherOrder(voucherId);等于直接return this.createVoucherOrder(voucherId);指的是非代理對象//也就是目標對象,也就是沒有事務功能// 所以我們要拿到事務代理的對象才行IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();//拿到當前對象的代理對象return proxy.createVoucherOrder(voucherId);//這里我們直接用代理對象proxy.createVoucherOrder(voucherId);,而不是直接this.createVoucherOrder(voucherId);
//          這樣proxy就是由Spring創建的,proxy就是由Spring管理的,就可以帶上事務功能
//            這么寫代碼還要在Spring啟動類加上@EnableAspectJAutoProxy(proxyTargetClass = true)}
//        7.返回訂單id}

這里是一個項目難點,在于為什么要userId.toString().inter(),直接userId.toString()不行么?原因在于如果是不同的線程來了,都是一個userId,那么每次toString()都是全新的String對象,JVM不是使用了equals方法去判斷是不是同一個共享資源,而是采用了對象頭信息去判斷(來自于ChatGPT-4o的回答)。
synchronized 關鍵字在 Java 中用于實現同步塊或方法,以確保多線程環境下對共享資源的訪問是線程安全的。它通過在進入同步塊或方法時獲取對象鎖(也稱為監視器鎖)來實現這一點。

難點1:synchronize

synchronized 的底層實現

synchronized 的底層實現依賴于 JVM 的內部機制。具體來說,它使用對象頭(Object Header)中的鎖信息來判斷鎖的對象是不是同一個對象。下面是一些關鍵點:

  1. 對象頭(Object Header)

    • 每個 Java 對象在內存中都有一個對象頭。對象頭中包含了對象的元數據,包括鎖的信息。
    • 對象頭的結構因 JVM 實現而異,但通常包含一個 Mark Word,用于存儲鎖標志位、哈希碼、GC 信息等。
  2. 鎖的狀態

    • Java 中的鎖有多種狀態,如無鎖(Unlocked)、偏向鎖(Biased Lock)、輕量級鎖(Lightweight Lock)、重量級鎖(Heavyweight Lock)。
    • 鎖的狀態轉換和鎖的獲取釋放都是通過對象頭中的 Mark Word 來管理的。
  3. 鎖的判斷

    • 當一個線程進入同步塊或方法時,JVM 會嘗試獲取對象頭中的鎖。如果對象已經被其他線程鎖定,當前線程會被阻塞,直到鎖被釋放。
    • JVM 通過比較對象頭中的 Mark Word 來判斷鎖的對象是不是同一個對象。如果兩個同步塊的鎖對象是同一個對象,那么它們的對象頭的 Mark Word 應該相同。

鎖的具體操作

當線程執行 synchronized (obj) 時:

  1. 鎖的獲取

    • 檢查對象頭中的 Mark Word 以確定當前鎖的狀態。
    • 如果對象是無鎖狀態,JVM 會嘗試使用 CAS(Compare And Swap)操作將對象頭的 Mark Word 更新為當前線程的鎖信息,表示當前線程獲得了該對象的鎖。
    • 如果對象已經被鎖定,JVM 會根據鎖的狀態(如偏向鎖、輕量級鎖、重量級鎖)執行相應的鎖獲取邏輯,可能會導致線程阻塞或自旋。
  2. 鎖的釋放

    • 當線程退出同步塊或方法時,JVM 會更新對象頭中的 Mark Word 以釋放鎖。
    • 如果有其他線程在等待該鎖,JVM 會通知等待線程重新嘗試獲取鎖。

舉例說明

synchronized (userId.toString().intern()) {// critical section
}

在這段代碼中:

  1. userId.toString().intern() 返回一個字符串對象的引用。
  2. synchronized 會嘗試獲取該字符串對象的鎖。
  3. 對象頭 中的 Mark Word 會記錄當前線程對該對象的鎖定信息。
  4. 當另一個線程執行相同的 synchronized 塊時,JVM 會檢查該字符串對象的 Mark Word,發現它已被鎖定,從而進行相應的處理(如阻塞或自旋)。

結論

synchronized 通過對象頭中的 Mark Word 來判斷鎖的對象是否相同,并通過鎖狀態的轉換和管理來實現線程同步。這確保了多個線程在訪問同一個共享資源時,不會出現線程安全問題。因此如果是"5".toString()的話,這里舉的例子是userId=5,那么每次toString出來的String對象都是不一樣的,我們的Mark Word也是不一樣的,因此無法保證是同一個共享資源,因此需要添加intern()方法,保證直接引用的是常量池里面的內容!

難點2:動態代理和@Transactional失效問題

抽取上面代碼塊關鍵兩行代碼進行解讀:

IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();//拿到當前對象的代理對象
return proxy.createVoucherOrder(voucherId);//這里我們直接用代理對象proxy.createVoucherOrder(voucherId);,而不是直接

VoucherOrderServiceImpl的createVoucherOrder方法是@Transactional的,但是VoucherOrderServiceImpl的seckillVoucher方法是沒有@Transactional的,如果沒有@Transactional的方法調用了@Transactional的方法,會導致@Transactional方法失效,如果發生意外是無法回滾的,
如果直接return createVoucherOrder(voucherId);拿到的是當前VoucherOrderServiceImpl對象,而不是他的代理對象
注意seckillVoucher方法沒有加@Transactional,會導致如果創建訂單失敗,則不會回滾,所以需要加@Transactional
事務要想生效,是Spring對VoucherOrderServiceImpl做了動態代理,拿到了他的代理對象,用createVoucherOrder(voucherId);做的事務處理
直接return createVoucherOrder(voucherId);等于直接return this.createVoucherOrder(voucherId);指的是非代理對象
也就是目標對象,也就是沒有事務功能
所以我們要拿到事務代理的對象才行
在 Spring 框架中,@Transactional 注解用于聲明式事務管理。了解 @Transactional 的工作原理和調用關系對于確保事務管理有效至關重要。

@Transactional 工作原理

Spring 的事務管理是通過 AOP(面向方面編程)實現的。@Transactional 注解的實現依賴于 Spring 的事務代理機制。代理對象會在方法調用前后進行事務的開啟、提交或回滾操作。

關鍵點

  1. 代理對象@Transactional 依賴于 Spring 創建的代理對象來管理事務。當一個事務方法被調用時,實際上調用的是代理對象的方法,代理對象負責處理事務邏輯。
  2. 方法內部調用:如果一個非事務方法調用另一個標記了 @Transactional 的方法(在同一個類中),由于是直接調用,沒有經過代理對象,事務不會生效。這是因為代理對象只能攔截外部調用,而無法攔截類內部的自調用。

示例分析

假設有以下類:

@Service
public class MyService {@Transactionalpublic void transactionalMethod() {// transactional code}public void nonTransactionalMethod() {transactionalMethod();}
}

在這個例子中:

  • 如果外部類(或外部對象)調用 nonTransactionalMethod(),事務管理不會生效,因為 nonTransactionalMethod() 直接調用了 transactionalMethod(),繞過了代理對象。
  • 如果外部類(或外部對象)直接調用 transactionalMethod(),事務管理會生效,因為調用通過了代理對象。

正確的使用方式

為了確保事務管理有效,可以采取以下策略:

  1. 外部調用
    確保事務方法通過代理對象調用。可以將事務方法放在另一個類中,從外部調用它們。

  2. 自調用解決方案
    使用 @Autowired 注入當前類的代理對象,確保內部調用也通過代理對象完成。

    @Service
    public class MyService {@Autowiredprivate MyService self;@Transactionalpublic void transactionalMethod() {// transactional code}public void nonTransactionalMethod() {self.transactionalMethod(); // 使用代理對象調用事務方法}
    }
    

結論

  1. 非事務方法調用事務方法

    • 事務管理不會生效,因為調用沒有經過代理對象。
    • 這種情況下 @Transactional 注解會失效。
  2. 事務方法調用事務方法

    • 事務管理會生效,但前提是調用通過代理對象進行。
    • 如果事務方法內部調用另一個事務方法,必須確保調用通過代理對象,否則事務管理仍然可能失效。

建議

為了確保 @Transactional 注解有效,盡量通過外部類或注入的代理對象來調用事務方法,避免在同一個類內部直接調用帶有 @Transactional 注解的方法。

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

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

相關文章

AI胡言亂語

復合矢量場在多維時空折疊過程中生成了高維拓撲映射&#xff0c;使得納米級別的存儲單元能夠在低能耗狀態下實現高效數據交換。基于相位調制的光子流動控制確保了全息影像的即時重構&#xff0c;同時動態適應不同頻段的干擾信號&#xff0c;達到最佳信噪比。 異相態轉化算法在…

基于Istio的多網關運行時:配置、部署和應用

1. 引言 Istio是一個開源的服務網格&#xff0c;主要應用于簡化微服務架構中的服務間通信、提供強大的監控能力以及加強服務的安全管理。通過利用Sidecar模式部署的Envoy代理&#xff0c;Istio能夠在幾乎無需修改服務代碼的情況下&#xff0c;實現服務發現、負載均衡、加密通信…

【LinuxC語言】管理者線程函數

文章目錄 前言工作者工作流程函數實現實現原理函數代碼概況總結前言 在并發編程中,管理者線程函數是一個重要的組成部分,它負責管理和調度工作線程。在Linux C語言環境下,我們可以使用POSIX線程庫(pthread)來創建和控制管理者線程。管理者線程通常負責添加任務到任務隊列…

WRF學習——使用CMIP6數據驅動WRF/基于ncl與vdo的CMIP6數據處理

動力降尺度 國際耦合模式比較計劃&#xff08;CMIP&#xff09;為研究不同情景下的氣候變化提供了大量的模擬數據&#xff0c;而在實際研究中&#xff0c;全球氣候模式輸出的數據空間分辨率往往較低&#xff08;>100Km&#xff0c;缺乏區域氣候特征&#xff0c;為了更好地研…

有哪些在本地運行大模型的方法

前言 在本文中&#xff0c;我們將看到在本地運行任何 LLM 的不同方法 1/ LMStudio LM Studio 是一款桌面應用程序&#xff0c;用于在計算機上運行本地 LLM。鏈接&#xff1a;https://lmstudio.ai/ 2/ Ollama Ollama 是一款工具&#xff0c;可讓您在機器上本地運行開源大型語…

vue項目靜態圖片下載

正常情況下只需要傳入圖片路徑就可以進行下載 methods: {downs(path, name) {//必須同源才能下載var alink document.createElement("a");alink.href path;alink.download name; //圖片名alink.click();},}, 但是當我們downs方法中直接傳入"/assets/load/xx…

二、分布式軟總線是如何高效的傳輸數據和任務的

分布式軟總線在HarmonyOS中高效傳輸數據和任務主要依靠以下幾個關鍵技術點和設計原則: 設備快速發現與連接: 利用多種通信技術(如Wi-Fi、藍牙、有線連接等),結合廣播、多播及服務發現協議,實現設備間的快速發現與穩定連接。這包括設備的唯一標識管理、網絡條件自適應選擇…

【pytorch14】感知機

單層感知機模型 對于單層的感知機&#xff0c;它的激活函數是一個sigmoid 對于符號的定義做一個規范化&#xff0c;輸入層每一層進行一個編號 輸入是第0層&#xff0c;上標0表示屬于輸入層&#xff0c;下標0到n表示一共有n個節點(這里嚴格來說應該是0~n-1&#xff0c;為了書寫…

一站式廣告監測新體驗,Xinstall助你廣告投放更精準

在這個移動互聯網飛速發展的時代&#xff0c;App推廣與運營成為了每個開發者與廣告主關注的焦點。然而&#xff0c;面對琳瑯滿目的廣告平臺和復雜的投放環境&#xff0c;如何精準評估廣告效果、優化投放策略&#xff0c;成為了擺在面前的一道難題。今天&#xff0c;我們就來聊聊…

Jemeter--關聯接口壓測

Jemeter–獨立不變參接口壓測 Jemeter–獨立變參接口壓測 Jemeter–關聯接口壓測 案例分析 比如&#xff1a;有個波次復核接口很慢&#xff0c;優化后需要壓測。但是波次復核接口數據是由另外兩個接口&#xff08;配單詳情、內盒信息&#xff09;的數據組合而來&#xff0c;而…

排序題目:三個數的最大乘積

文章目錄 題目標題和出處難度題目描述要求示例數據范圍 解法一思路和算法代碼復雜度分析 解法二思路和算法代碼復雜度分析 題目 標題和出處 標題&#xff1a;三個數的最大乘積 出處&#xff1a;628. 三個數的最大乘積 難度 3 級 題目描述 要求 給定一個整數數組 nums …

fastadmin最新版導出數據時 表格中會有 html標簽的解決辦法

fastadmin 自帶的導出方法&#xff0c; 是一個純前端的導出&#xff0c; 沒有請求后臺的接口 當我們使用導出功能時&#xff0c; 有些數據&#xff0c; 我們在設計的時候&#xff0c;配置的是 枚舉類型的 但是當我們導出數據的時候&#xff0c; 居然導出的數據中帶有 html 的…

使用el-col和el-row布局,有版心,一頁有兩欄布局 三欄布局 四欄布局 使用vue動態渲染元素

使用Vue結合Element UI的el-row和el-col組件來實現版心布局&#xff0c;并動態渲染不同欄數的布局&#xff0c;可以通過以下步驟實現&#xff1a; 定義版心容器&#xff1a;使用el-container來定義整個頁面的容器&#xff0c;其中el-header、el-main、el-footer分別定義頭部、主…

k8s-第十節-Ingress

Ingress 介紹 Ingress 為外部訪問集群提供了一個 統一 入口&#xff0c;避免了對外暴露集群端口&#xff1b;功能類似 Nginx&#xff0c;可以根據域名、路徑把請求轉發到不同的 Service。可以配置 https 跟 LoadBalancer 有什么區別&#xff1f; LoadBalancer 需要對外暴露…

Promise解決異步編程問題

一個典型的異步編程問題&#xff1a;即您嘗試在循環中發起多個異步請求&#xff0c;并希望在所有請求都完成后執行某些操作。然而&#xff0c;由于JavaScript的異步性質&#xff0c;num和total的比較在循環結束時立即執行&#xff0c;而不是在所有請求都完成后執行。這可能導致…

【12321騷擾電話舉報受理中心-短信驗證安全分析報告】

前言 由于網站注冊入口容易被黑客攻擊&#xff0c;存在如下安全問題&#xff1a; 暴力破解密碼&#xff0c;造成用戶信息泄露短信盜刷的安全問題&#xff0c;影響業務及導致用戶投訴帶來經濟損失&#xff0c;尤其是后付費客戶&#xff0c;風險巨大&#xff0c;造成虧損無底洞…

開發常識:命令行終端、庫源碼、開發環境階段

目錄 命令行終端 集成開發環境&#xff08;IDE &#xff09;&#xff1a;有插件校驗等限制&#xff0c;成功率低于操作系統 庫源碼 github上搜 官網 UNPKG托管開源的包 專業名詞 環境 開發&#xff1a;本地機 開發和調試 生產&#xff1a;最終部署 測試&#xff1a;…

交流負載箱的主要功能有哪些?

交流負載箱可以模擬各種實際用電設備的功率、電流、電壓等參數&#xff0c;使得電源系統在運行過程中能夠承受實際負載的考驗&#xff0c;確保電源系統的穩定運行。通過交流負載箱對電源設備進行測試&#xff0c;可以檢測出電源設備在過載、短路等異常情況下的保護功能是否正常…

Linux和mysql中的基礎知識

cpu讀取的指令大部分在內存中&#xff08;不考慮緩存&#xff09; 任何程序在運行之前都的加入到內存。 eip->pc指針&#xff0c;指明當前指令在什么位置。 代碼大概率是從上往下執行的&#xff0c;基于這樣的基本理論。既可以將一部分指令加載到CPU對應的緩存中&#xf…

解決zip文件中文亂碼問題

后臺微服務運行在linux環境里&#xff0c;前端Vue。在一個項目中&#xff0c;把后臺的文件打包成zip&#xff0c;下載到前臺。結果發現zip文件名本身亂碼&#xff0c;zip文件內壓縮的文件也是亂碼。所謂亂碼&#xff0c;程序員都見過&#xff0c;就是中文變成了亂七八糟的字符。…