JUC并發編程(二)Monitor/自旋/輕量級/鎖膨脹/wait/notify/鎖消除

目錄

一 基礎

1 概念

2?賣票問題

3 轉賬問題

二 鎖機制與優化策略

?0?Monitor?

1 輕量級鎖

2 鎖膨脹

3 自旋

4 偏向鎖

5 鎖消除

6 wait /notify

7 sleep與wait的對比

8 join原理


一 基礎

1 概念

臨界區

一段代碼塊內如果存在對共享資源的多線程讀寫操作,撐這段代碼區為臨界區。

競態條件

多個線程在臨界區內執行,由于代碼的執行序列不同而導致結果無法預測,稱之為發生了競態條件

為了避免臨界區的競態條件發生,有多種手段可以達到目的

  • 阻塞式的解決方案:synchronized,Lock
  • 非阻塞式的解決方案:原子變量

2?賣票問題

代碼實現:

首先代碼實現了一個窗口類實現對賣票的相關業務邏輯,其次在主方法當中定義多個線程實現對票的購買,實現了sell方法的安全,random隨機數的安全,集合的安全,同時利用.join方法等待所有線程執行結束。

package day01.mysafe;import java.util.ArrayList;
import java.util.List;import java.util.Vector;
import java.util.concurrent.ThreadLocalRandom;public class example1 {static int randomAmount() {return ThreadLocalRandom.current().nextInt(1, 6);}public static void main(String[] args) throws InterruptedException {//模擬賣票List<Integer> arr = new Vector<>();List<Thread> threads = new ArrayList<>();TicketWindow ticketWindow = new TicketWindow(1000);for (int i = 0; i < 2200; i++) {Thread t = new Thread(() -> {int num = ticketWindow.sell(randomAmount());arr.add(num);});threads.add(t);t.start();}//等待所有線程執行完for (Thread thread : threads) {thread.join();}System.out.println("剩余:" + ticketWindow.getAmount());System.out.println("賣出:" + arr.stream().mapToInt(x -> x == null ? 0 : x).sum());}
}/*** 窗口類*/
class TicketWindow {private int amount;public TicketWindow(int number) {this.amount = number;}public int getAmount() {return amount;}/*** 賣票*/public synchronized int sell(int amount) {if (this.amount >= amount) {this.amount -= amount;return amount;} else {return 0;}}
}

3 轉賬問題

加實例鎖

鎖的是當前對象,每個對象都有獨立的鎖,只影響一個實例的并發操作,多個實例可以并發進行。會出現死鎖問題,當線程1 獲取 a的鎖,將a鎖住需要修改b但是需要b的鎖,此時需要等待b的鎖,但是同時線程2獲取b的鎖,將b鎖住需要修改a但是需要a的鎖,兩個線程相互等待,持續僵持導致死鎖。

import java.util.concurrent.ThreadLocalRandom;public class example2 {static int random() {return ThreadLocalRandom.current().nextInt(1, 100);}public static void main(String[] args) throws InterruptedException {Amount a = new Amount(1000);Amount b = new Amount(1000);Thread t1 = new Thread(() -> {for (int i = 0; i < 100; i++) {a.transfer(b, random());}}, "t1");Thread t2 = new Thread(() -> {for (int i = 0; i < 100; i++) {b.transfer(a, random());}}, "t2");t1.start();t2.start();t1.join();t2.join();System.out.printf("余額為%d\n", b.getMoney() + a.getMoney());}}class Amount {private int money;public Amount(int money) {this.money = money;}public int getMoney() {return money;}public void setMoney(int money) {this.money = money;}//轉賬 (向a賬戶轉賬money元)public synchronized void transfer(Amount a, int money) {if (this.money >= money) {this.money -= money;a.money += money;}}}

加類鎖

import java.util.concurrent.ThreadLocalRandom;public class example2 {static int random() {return ThreadLocalRandom.current().nextInt(1, 100);}public static void main(String[] args) throws InterruptedException {Amount a = new Amount(1000);Amount b = new Amount(1000);Thread t1 = new Thread(() -> {for (int i = 0; i < 100; i++) {a.transfer(b, random());}},  "t1");Thread t2 = new Thread(() -> {for (int i = 0; i < 100; i++) {b.transfer(a, random());}},  "t2");t1.start();t2.start();t1.join();t2.join();System.out.printf("余額為%d\n", b.getMoney()+a.getMoney());}}class Amount {private int money;public Amount(int money) {this.money = money;}public int getMoney() {return money;}public void setMoney(int money) {this.money = money;}//轉賬 (向a賬戶轉賬money元)public void transfer(Amount a, int money) {synchronized (Amount.class){if (this.money >= money) {this.money -= money;a.money += money;}}}}

二 鎖機制與優化策略

?0?Monitor?

Monitor被翻譯為監視器或管程。Monitor 是 JVM 實現?synchronized?的核心機制,通過 EntryList、WaitSet 和 Owner 管理線程對鎖的訪問。

當線程首次通過?synchronized?競爭 obj 的鎖時,JVM 會在底層為其關聯一個 Monitor,如果Owner沒有對應的線程,則會成功獲取線程鎖,否則進入EntryList阻塞排隊. (同一對象使用synchroized)

下面介紹線程持有鎖并執行 wait/sleep 的運作狀態(sleep可以在沒有鎖的狀態運行,無鎖就只釋放CPU,有鎖釋放CPU,但鎖不釋放)

  • 鎖的爭搶(進入EntryList)→ 持有(成為Owner)→ <wait>? 主動讓出,將鎖釋放(進入WaitSet)→ <notify> 喚醒后重新競爭->(進入EntryList)。

  • 鎖的爭搶(進入EntryList)-->持有Owner -> <sleep> 主動讓出CPU時間片,不釋放鎖,變為TIMED_WAITING狀態同時維持Owner身份->時間結束后自動恢復運行,無需重新進入EntryList競爭。

Monitor 由以下核心組件構成:

  • Owner(持有者):當前持有鎖的線程。
  • EntryList(入口隊列):等待獲取鎖的線程隊列。
  • WaitSet(等待隊列):調用?wait()?方法后釋放鎖的線程隊列。

1 輕量級鎖

輕量級鎖:如果一個對象雖然有多線程訪問,但多線程訪問的時間是錯開的(也就沒有競爭),那么就可以使用輕量級鎖來優化。

輕量級鎖對使用者是透明的,語法依舊是synchroized,不需要人工干預。

  • ?低競爭時用輕量級鎖:當多線程競爭較小時(如交替執行同步代碼),JVM 會優先使用輕量級鎖(基于 CAS 操作),避免直接使用重量級鎖(Monitor)的性能開銷

  • ?競爭加劇時升級:如果輕量級鎖的 CAS 操作失敗(其他線程同時競爭),JVM 會自動將其升級為重量級鎖(通過操作系統互斥量實現阻塞)。

2 鎖膨脹

鎖膨脹是 JVM 在并發壓力增大時,將輕量級鎖升級為重量級鎖的過程,以犧牲部分性能換取線程安全。

觸發條件:

  • 輕量級鎖競爭失敗:當多個線程同時競爭輕量級鎖(CAS 操作失敗),JVM 會將鎖升級為重量級鎖。

  • 調用?wait()/notify():這些方法需要重量級鎖(Monitor)的支持,會強制觸發膨脹。

  • HashCode 沖突:若對象已計算哈希碼,無法再使用偏向鎖或輕量級鎖,直接膨脹。

3 自旋

概念:自旋是“不停嘗試”的鎖獲取策略

當首個線程獲取輕量級鎖后,第二個嘗試訪問的線程不會立即阻塞或促使鎖升級,而是先進入自旋狀態,等待原先的線程釋放鎖。若在自旋期間鎖被釋放,則該線程可直接獲得鎖,避免進入阻塞狀態及觸發鎖升級至重量級鎖,從而提高效率并減少資源消耗。這種機制有效降低了因鎖升級帶來的性能損耗,確保了在并發環境下的高效運行。

4 偏向鎖

偏向鎖是Java虛擬機(JVM)中一種針對同步操作的優化技術,主要用于減少無競爭情況下的同步開銷。它是JVM鎖升級機制的第一階段(無鎖→偏向鎖→輕量級鎖→重量級鎖)。

在JDK15及以后版本,由于現代硬件性能提升和其他優化技術的出現,偏向鎖默認被禁用,因為其帶來的收益已經不明顯,而撤銷開銷在某些場景下可能成為負擔。

偏向鎖與輕量級鎖之間的對比

偏向鎖輕量級鎖
針對無競爭場景(同一線程多次獲取鎖)針對低競爭場景(多個線程交替執行,無并發沖突)
消除整個同步過程的開銷避免操作系統互斥量(Mutex)的開銷

偏向鎖的核心機制

  • 首次獲取鎖
    通過一次?CAS操作?將線程ID寫入對象頭的Mark Word,之后該線程進入同步塊無需任何原子操作。

  • 無競爭時
    執行同步代碼就像無鎖一樣(僅檢查線程ID是否匹配)。

  • 遇到競爭
    觸發偏向鎖撤銷(需暫停線程),升級為輕量級鎖。

輕量級鎖的核心機制

  • 加鎖過程

    1. 在棧幀中創建鎖記錄(Lock Record)

    2. 用CAS將對象頭的Mark Word復制到鎖記錄中

    3. 再用CAS將對象頭替換為指向鎖記錄的指針(成功則獲取鎖)

  • 解鎖過程
    用CAS將Mark Word還原回對象頭(若失敗說明存在競爭,升級為重量級鎖)。

?關鍵差異

  • 偏向鎖:全程只需1次CAS(首次獲取時)

  • 輕量級鎖:每次進出同步塊都需要CAS(加鎖/解鎖各1次)

5 鎖消除

鎖消除是JVM中一項重要的編譯器優化技術,它通過移除不必要的同步操作來提升程序性能。這項技術主要解決"無實際競爭情況下的無效同步"問題。

鎖消除基于逃逸分析(Escape Analysis)?技術:

  1. JVM在運行時分析對象的作用域

  2. 判斷對象是否會"逃逸"出當前線程(即被其他線程訪問)

  3. 如果確認對象不會逃逸(線程私有),則消除該對象的所有同步操作

public String concatStrings(String s1, String s2) {// StringBuilder是方法內部的局部變量StringBuilder sb = new StringBuilder();sb.append(s1);  // 內部有synchronized塊sb.append(s2);  // 內部有synchronized塊return sb.toString();
}
  1. StringBuilder實例sb是方法局部變量

  2. 逃逸分析確認sb不會逃逸出當前線程(不會被其他線程訪問)

  3. JIT編譯器會消除所有synchronized同步操作

6 wait /notify

首先涉及三個組件,Owner,EntryList,WaitSet。

組件存儲線程狀態觸發條件是否持有鎖位置轉移方向
OwnerRUNNABLE成功獲取鎖→ WaitSet (wait()時)
EntryListBLOCKED競爭鎖失敗← WaitSet (notify()后)
WaitSetWAITING主動調用wait()→ EntryList (被喚醒后)

一個線程進入時首先會嘗試獲取Owner權,也就是獲取鎖,但是同一時刻只能有一個線程持有鎖,獲取成功可以直接執行臨界代碼區,獲取失敗的線程待在EntryList當中,處于Blocked阻塞狀態,在持有鎖階段可以使用wait方法,會使當前鎖釋放,并進入WaitSet當中,處于Waiting等待狀態,其必須使用notify/notifyAll喚醒才可進入EntryList當中,從而再次得到競爭Owner的權力。

代碼示意

代碼開啟兩個線程,對同一個對象實例加鎖,線程1進入鎖后,執行wait進入WaitSet進入阻塞等待,線程1將鎖釋放,此時線程2獲取到鎖,將線程1喚醒,線程1將后續代碼執行結束。

  • 在線程2當中睡眠3s一定程度上確保其在線程1之后執行(一定程度上避免出現永久阻塞等待的狀態),線程1阻塞等待,線程2喚醒。
  • 線程1被喚醒之后會將線程當中剩余的代碼執行結束,然后進入EntryList中。
  • wait可加參數,相當于設置一個超時時間,在這個期間中等待,超時自動釋放。
  • 在類文件當中加入一個Boolean標志位可以防止虛假喚醒的出現,虛假喚醒指的是在沒有明確使用notify/notifyAll對線程進行喚醒的條件下而被喚醒或者喚醒的并不是想要的。(借助while循環持續判斷)
package day01.mysynchronized;public class Example4 {static final Object obj = new Object();static boolean isSignaled = false; // 新增標志位public static void main(String[] args) {System.out.println("線程1開始執行");Thread t1 = new Thread(() -> {try {synchronized (obj) {System.out.println("線程1處于等待狀態....");// 循環檢查標志位,防止虛假喚醒while (!isSignaled) {obj.wait();}System.out.println("線程1執行結束");}} catch (InterruptedException e) {Thread.currentThread().interrupt();}}, "t1");Thread t2 = new Thread(() -> {System.out.println("線程2開始執行,睡眠3秒...");try {Thread.sleep(3000);} catch (InterruptedException e) {Thread.currentThread().interrupt();}synchronized (obj) {System.out.println("線程2對線程1進行喚醒");isSignaled = true; // 設置標志位為trueobj.notify();System.out.println("線程2執行結束,線程1被喚醒");}}, "t2");t2.start();t1.start();}
}

運行展示:

7 sleep與wait的對比

1 對于參數含義的不同

sleep(0)是一種主動讓出時間片的過程,而wait(0) /wait() 是指長時間等待

2 調用位置的要求

sleep可以在任意位置調用,而wait必須在同步代碼塊當中調用。

3 喚醒機制

sleep:interrupt或者超時喚醒

wait:其他線程使用notify/notifyAll或者超時喚醒

4 線程改變的狀態不同

線程持有鎖并執行sleep,當前線程并不會釋放當前持有的鎖,而是攜帶鎖休眠一段時間,持續處于Owner狀態,休眠結束會繼續執行代碼邏輯。

線程持有并鎖執行wait時,當前線程會釋放當前持有的鎖,并從持有管程Monitor轉移到WaitSet等待隊列當中,其他線程可以獲取鎖的持有權,可借助notify/notifyAll將鎖喚醒,從WaitSet等待隊列進入EntryList鎖競爭隊列當中。

8 join原理

join()?方法的實現基于 Java 的?等待-通知機制?和?線程狀態管理

Thread.join()?的核心原理:

  1. 基于 Java 內置鎖(synchronized)

  2. 使用等待-通知機制(wait/notify)

  3. 依賴 JVM 的線程終止通知

  4. 通過循環檢查確保正確性

Thread.join() 通過 內置鎖 確保線程安全,利用 等待-通知機制 實現阻塞與喚醒,依賴 JVM 的線程終止通知 自動觸發喚醒,并通過 循環檢查 防止虛假喚醒,最終實現線程間的有序協作。

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

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

相關文章

Doris 與 Elasticsearch:誰更適合你的數據分析需求?

一、Doris 和 Elasticsearch 的基本概念 &#xff08;一&#xff09;Doris 是什么&#xff1f; Doris 是一個用于數據分析的分布式 MPP&#xff08;大規模并行處理&#xff09;數據庫。它主要用于存儲和分析大量的結構化數據&#xff08;比如表格數據&#xff09;&#xff0c…

使用Virtual Serial Port Driver+com2tcp(tcp2com)進行兩臺電腦的串口通訊

使用Virtual Serial Port Drivercom2tcp或tcp2com進行兩臺電腦的串口通訊 問題說明解決方案方案三具體操作流程網上教程軟件安裝拓撲圖準備工作com2tcp和tcp2com操作使用串口助手進行驗證 方案三存在的問題數據錯誤通訊延時 問題說明 最近想進行串口通訊的一個測試&#xff0c…

transformer和 RNN以及他的幾個變體區別 改進

Transformer、RNN 及其變體&#xff08;LSTM/GRU&#xff09;是深度學習中處理序列數據的核心模型&#xff0c;但它們的架構設計和應用場景有顯著差異。以下從技術原理、優缺點和適用場景三個維度進行對比分析&#xff1a; 核心架構對比 模型核心機制并行計算能力長序列依賴處…

CSS6404L 在物聯網設備中的應用優勢:低功耗高可靠的存儲革新與競品對比

物聯網設備對存儲芯片的需求聚焦于低功耗、小尺寸、高可靠性與傳輸效率&#xff0c;Cascadeteq 的 CSS6404L 64Mb Quad-SPI Pseudo-SRAM 憑借差異化技術特性&#xff0c;在同類產品中展現顯著優勢。以下從核心特性及競品對比兩方面解析其應用價值。 一、CSS6404L 核心產品特性…

go語言map擴容

map是什么&#xff1f; ?在Go語言中&#xff0c;map是一種內置的無序key/value鍵值對的集合&#xff0c;可以根據key在O(1)的時間復雜度內取到value&#xff0c;有點類似于數組或者切片結構&#xff0c;可以把數組看作是一種特殊的map&#xff0c;數組的key為數組的下標&…

2025年SDK游戲盾實戰深度解析:防御T級攻擊與AI反作弊的終極方案

一、引言&#xff1a;游戲安全的“生死防線” 2025年&#xff0c;全球游戲行業因DDoS攻擊日均損失3.2億元&#xff0c;攻擊峰值突破8Tbps&#xff0c;且70% 的攻擊為混合型&#xff08;DDoSCC&#xff09;。傳統高防IP因延遲高、成本貴、協議兼容性差&#xff0c;已無法滿足實…

【Linux】LInux下第一個程序:進度條

前言&#xff1a; 在前面的文章中我們學習了LInux的基礎指令 【Linux】初見&#xff0c;基礎指令-CSDN博客【Linux】初見&#xff0c;基礎指令&#xff08;續&#xff09;-CSDN博客 學習了vim編輯器【Linux】vim編輯器_linux vim insert-CSDN博客 學習了gcc/g【Linux】編譯器gc…

Web前端基礎

### 一、瀏覽器 火狐瀏覽器、谷歌瀏覽器(推薦)、IE瀏覽器 推薦谷歌瀏覽器原因&#xff1a; 1、簡潔大方,打開速度快 2、開發者調試工具&#xff08;右鍵空白處->檢查&#xff0c;打開調試模式&#xff09; ### 二、開發工具 核心IDE工具 1. Visual Studio Code (VS Code)?…

C++調試(肆):WinDBG分析Dump文件匯總

目錄 1.前言 2.WinDBG中常用的指令 3.分析異常時要關注的信息 4.心得 前言 本篇博客主要針如何使用WinDBG工具調試Dump文件的流程進行一個講解&#xff0c;具體捕獲的Dump文件也是前兩節例子中生成的Dump文件。 WinDBG中常用的指令 關于WinDBG調試時常用的指令主要分為以下幾種…

SOC-ESP32S3部分:33-聲學前端模型ESP-SR

飛書文檔https://x509p6c8to.feishu.cn/wiki/YnbmwtqI5iBwE3kHA7AcZ3yTnLf ESP-SR 是樂鑫官方開發的一個音頻組件&#xff0c;支持以下模塊&#xff1a; 聲學前端算法 AFE喚醒詞檢測 WakeNet命令詞識別 MultiNet語音合成&#xff08;目前只支持中文&#xff09; 組件地址&am…

基于vscode,idea,java,html,css,vue,echart,maven,springboot,mysql數據庫,在線考試系統

詳細視頻&#xff1a;【基于vscode,idea,java,html,css,vue,echart,maven,springboot,mysql數據庫&#xff0c;在線考試系統-嗶哩嗶哩】 https://b23.tv/7hwmwmQ

【Linux】shell中的運行流程控制

目錄 一.什么是運行流程控制 二.條件允許流程控制--if 2.1.單分支 2.2.雙分支 2.3.多分支 if多分支練習 三.循環運行流程控制 無判定循環--for 判斷循環--while&#xff0c;until 四.選擇運行流程控制 五.自動應答--expect 5.1.固定位置的交互應答 5.2.非固定位置的…

新能源汽車熱管理核心技術解析:冬季續航提升40%的行業方案

新能源汽車熱管理核心技術解析&#xff1a;冬季續航提升40%的行業方案 摘要&#xff1a;突破續航焦慮的關鍵在熱能循環&#xff01; &#x1f449; 本文耗時72小時梳理行業前沿方案&#xff0c;含特斯拉/比亞迪等8家車企熱管理系統原理圖 一、熱管理為何成新能源車決勝關鍵&am…

OCR MLLM Evaluation

為什么需要評測體系&#xff1f;——背景與矛盾 ?? 能干的事&#xff1a;?? 看清楚發票、身份證上的字&#xff08;準確率>90%&#xff09;&#xff0c;速度飛快&#xff08;眨眼間完成&#xff09;。??干不了的事&#xff1a;?? 碰到復雜表格&#xff08;合并單元…

深入解析JVM工作原理:從字節碼到機器指令的全過程

一、JVM概述 Java虛擬機(JVM)是Java平臺的核心組件&#xff0c;它實現了Java"一次編寫&#xff0c;到處運行"的理念。JVM是一個抽象的計算機器&#xff0c;它有自己的指令集和運行時內存管理機制。 JVM的主要職責&#xff1a; 加載&#xff1a;讀取.class文件并驗…

Python繪圖庫及圖像類型之特殊領域可視化

Python繪圖庫及圖像類型之基礎圖表-CSDN博客https://blog.csdn.net/weixin_64066303/article/details/148433762?spm1001.2014.3001.5501 Python繪圖庫及圖像類型之高級可視化-CSDN博客https://blog.csdn.net/weixin_64066303/article/details/148450750?spm1001.2014.3001.…

04 APP 自動化- Appium toast 元素定位列表滑動

文章目錄 一、toast 元素的定位二、滑屏操作 一、toast 元素的定位 toast 元素就是簡易的消息提示框&#xff0c;toast 顯示窗口顯示的時間有限&#xff0c;一般3秒左右 # -*- codingutf-8 -*- from time import sleep from appium import webdriver from appium.options.an…

C/C++ OpenCV 矩陣運算

C/C OpenCV 矩陣運算詳解 &#x1f4a1; OpenCV 是一個強大的開源計算機視覺和機器學習庫&#xff0c;它提供了豐富的矩陣運算功能&#xff0c;這對于圖像處理和計算機視覺算法至關重要。本文將詳細介紹如何使用 C/C 和 OpenCV 進行常見的矩陣運算。 矩陣的創建與初始化 在進…

基于大模型的 UI 自動化系統

基于大模型的 UI 自動化系統 下面是一個完整的 Python 系統,利用大模型實現智能 UI 自動化,結合計算機視覺和自然語言處理技術,實現"看屏操作"的能力。 系統架構設計 #mermaid-svg-2gn2GRvh5WCP2ktF {font-family:"trebuchet ms",verdana,arial,sans-…

USB擴展器與USB服務器的2個主要區別

在現代辦公和IT環境中&#xff0c;連接和管理USB設備是常見需求。USB擴展器&#xff08;常稱USB集線器&#xff09;與USB服務器&#xff08;如朝天椒USB服務器&#xff09;是兩類功能定位截然不同的解決方案。前者主要解決物理接口數量不足的“近身”連接擴展問題&#xff0c;而…