22.Volatile原理

文章目錄

  • Volatile原理
    • 1.Volatile語義中的內存屏障
      • 1.1.volatile寫操作的內存屏障
        • 1.1.1.StoreStore 屏障
        • 1.1.2.StoreLoad 屏障
      • 1.2.volatile讀操作的內存屏障
        • 1.2.1.LoadStore屏障
        • 1.2.2.LoadLoad屏障
    • 2.volatile不具備原子性
        • 2.1.原理

Volatile原理

1.Volatile語義中的內存屏障

在Java代碼中,volatile關鍵字主要又兩層語義

  1. 不同線程對volatile變量的值具有內存可見性,就是一個線程修改了某個volatile變量的值,該值對其他線程立即可見。
  2. 禁止指令重排序

同時volatile關鍵字不僅能保證可見性,還能保證有序性,保證有序性是通過內存屏障指令來確保的。

JVM編譯器會在生成字節碼文件時,會在指令序列中插入內存屏障來禁止特定類型的CPU重排序。

JVM在處理volatile關鍵字修飾的變量時,會采取保守策略來確保內存可見性和有序性,這涉及到內存屏障(Memory Barrier)的使用。內存屏障是一種硬件層面的指令,用于確保某些內存訪問操作的執行順序,防止CPU的亂序執行對并發程序的正確性產生影響。對于volatile變量的讀寫,JVM會分別在讀和寫操作前后插入適當類型的內存屏障,確保以下幾點:

  1. 全局可見性: 保證對volatile變量的寫操作能立即對其他線程可見,即使在不同的CPU緩存中也是如此。這意味著寫操作之后,修改的值會立刻刷出到主內存中。
  2. 禁止重排序: 防止編譯器和CPU對涉及volatile變量的代碼進行不必要的重排序,確保它們按照程序員指定的順序執行。這對于依賴于特定順序的并發控制邏輯至關重要。

基于保守策略的volatile操作內存屏障插入策略主要包括以下方面:

  • 寫屏障(Store Memory Barrier): 在寫入volatile變量之后插入。它的作用是確保在該屏障之前的所有普通寫操作(非volatile)都已完成,并且將當前線程的工作內存中的volatile變量值刷新到主內存中。這樣,任何讀取該volatile變量的線程都能看到最新值。
    • 在每個volatile操作面插入一個StoreStore屏障
    • 在每個volatile操作面插入一個 StoreLoad屏障
  • 讀屏障(Load Memory Barrier): 在讀取volatile變量之前插入。它的作用是確保讀取操作之后的加載不會被重排序到該屏障之前,并且使CPU讀取主內存中的最新值,而不是使用緩存中的舊值,從而確保讀取到的是最近一次寫入的值,無論這個寫入操作發生在哪個線程中。
    • 在每個volatile操作面插入一個LoadLoad屏障
    • 在每個volatile操作面插入一個 LoadStore屏障

這些屏障的聯合使用確保了對volatile變量的讀寫操作具有原子性和全局有序性,盡管它們不保證復合操作(如count++)的原子性。這就是為什么即使在沒有鎖的情況下,volatile也能作為輕量級的同步機制,用于狀態標記、雙重檢查鎖定模式等場景。

1.1.volatile寫操作的內存屏障

volatile寫操作的內存屏障插入策略為:在每個volatile寫操作前插入SotreStore(SS)屏障,在寫操作之后加上StoreLoad屏障

1.1.1.StoreStore 屏障

定義與作用StoreStore屏障主要用于確保一個存儲(寫)操作在另一個存儲操作之前完成。換句話說,它強制所有在該屏障之前的存儲操作在該屏障之后的存儲操作之前完成。這種屏障通常用于避免寫-寫沖突導致的數據不一致問題,尤其是在處理器有亂序執行能力的體系結構中。

  • 前面的寫入不會重排序到后面
  • 前面的寫指令完成后,高速緩存數據刷入主存
  • 后面的寫操作不會排序到前面

應用場景: 例如,在實現某些類型的鎖釋放操作時,可能需要確保解鎖操作前的所有寫操作已經完成,以免新獲得鎖的線程看到不一致的狀態。

1.1.2.StoreLoad 屏障

定義與作用StoreLoad屏障是Java內存模型中最強大的一種屏障,它確保在屏障之前的所有寫操作(存儲操作)在屏障之后的任何讀操作(加載操作)之前完成。這意味著不僅要求寫操作完成,而且要確保這些寫操作對所有線程可見。因此,StoreLoad屏障通常用于實現volatile變量的寫操作后,以確保寫入的值對其他線程立即可見。

  • 前面的寫不會重排序到后面
  • 前面的寫指令操作完成后,高速緩存數據立即刷入主存
  • 讓高速緩存的數據失效,重新從主存中加載數據,保證內核的高速緩存數據一致
  • 后面的讀操作不會重排序到前面

應用場景StoreLoad屏障直接關聯于Java中volatile字段的寫操作實現。當一個線程修改了一個volatile變量的值,JVM會在寫操作之后插入一個StoreLoad屏障,以確保該寫入的值能夠立即對其他線程可見,同時刷新處理器的緩存,避免數據的臟讀。此外,它也常用于鎖釋放操作后的內存可見性保障,確保解鎖前的內存更改對后續可能獲得鎖的線程是可見的。

總結來說,StoreStore屏障關注于維持存儲操作之間的順序,而StoreLoad屏障則進一步確保了寫操作的全局可見性,并在寫-讀操作間建立了一個順序關系,這對于維護多線程程序的一致性和正確性至關重要。

在這里插入圖片描述

1.2.volatile讀操作的內存屏障

volatile讀操作的內存屏障插入策略為:在每個volatile讀操作后面插入LoadLoad(LL)屏障和LoadStore屏障,禁止后面的普通讀、普通寫、和前面的volatile讀操作發生重排序

1.2.1.LoadStore屏障

定義與功能LoadStore屏障,也稱為讀寫屏障,其主要作用是確保屏障之后的讀操作不會被重排序到屏障之前,且屏障之后的寫操作不會被重排序到屏障之前的讀操作之前。這意味著它不僅確保了讀操作不會提前,還阻止了讀之后的寫操作與讀操作之前的任何寫操作發生亂序。這在volatile讀操作的上下文中,意味著確保了讀取到的volatile變量的值不會被之后的寫操作所覆蓋或影響,保持了讀取操作的確定性。

  • 前面的讀操作不會排到后面
  • 讓高速緩存中的數據失效,重新從主存中加載數據
  • 后面的寫操作不會排列到前面

在volatile讀操作中的應用: 當執行volatile讀操作時,Java虛擬機(JVM)會在讀取操作之后插入一個LoadStore屏障。這個屏障的目的是確保當前線程的任何后續寫操作不會與剛完成的volatile讀操作交錯,保證了volatile讀的值不會因為之后的寫而變得無效或不一致。同時,這也間接幫助確保了volatile讀取操作后的寫操作不會與之前的volatile讀操作或普通讀操作發生沖突,維護了操作的順序性。

1.2.2.LoadLoad屏障

定義與功能LoadLoad屏障,或稱為讀讀屏障,它的主要職責是防止屏障之后的讀操作被重排序到屏障之前的任何讀操作之前。這意味著它確保了在屏障之后執行的任何讀操作不會因為CPU的亂序執行優化而提前到屏障之前執行。盡管LoadLoad屏障本身在某些JMM的描述中不常直接提及,但討論內存屏障時,其概念往往隱含在維護讀操作順序性的討論中。

  • 前面的讀操作不會被排到后面
  • 讓高速緩存中的數據失效,重新從主存中加載數據
  • 后面讀操作不會排列到前面

在volatile讀操作中的應用: 雖然直接提及LoadLoad屏障在volatile讀操作后插入的情況較少見,通常強調的是LoadStoreStoreLoad屏障的作用,但理解其概念對于全面把握內存屏障如何維護順序性是有幫助的。在volatile讀操作的上下文中,可以抽象理解為,屏障的邏輯效果確保了讀取volatile變量的值不會被之后的其他讀取操作提前,保證了讀取volatile變量的順序性。不過,實際中,volatile讀操作的關鍵在于通過StoreLoad屏障確保了對其他線程寫入volatile變量的值立即可見,同時防止了volatile讀與普通讀寫操作的不恰當重排序。

在這里插入圖片描述

2.volatile不具備原子性

volatile能保證數據的可見性,但是volatile并不能完全保證數據的原子性。對于volatile類型的變量進行符合操作例如(i++),仍然會存在線程不安全的問題

/*** 使用 10個線程,每個線程進行1000次 ++操作,來觀察成員變量的結果是否符合我們的預期*/
public class VolatileAddDemo {private volatile int num = 0;@Test@DisplayName("測試并發情況下 volatile原子性")public void testVolatileAdd() {CountDownLatch latch = new CountDownLatch(10);ExecutorService executorService = Executors.newFixedThreadPool(10);for (int i = 0; i < 10; i++) {executorService.submit(() -> {for (int j = 0; j < 1000; j++) {num++;}// 每次執行完畢 -1latch.countDown();});}// 等待全部線程執行完畢try {latch.await();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("最終的結果:" + num);System.out.println("預期的結果:10000");System.out.println("相差:" + (10000 - num));}}

在這里插入圖片描述

2.1.原理

首先來看一下JMM對變量進行讀取和寫入的操作流程

在這里插入圖片描述

對于非volatile修飾的普通變量來說,在讀取變量的時候,JMM要求需要 報紙readload的順序即可

但是,從主存中讀取 x 、y 兩個變量的值,可能的操作是 read x -> read y -> load y -> load x,它并不要求操作是連續的

對于關鍵字 volatile修飾的內存可見變量而言,具有兩個重要的語義

  1. 使用volatile修飾的變量在變量值發生改變時,會立即同步到主存,并且讓其他線程的變量副本失效
  2. 禁止指令重排序:用volatile修飾的變量在硬件層面上會通過在指令前后加入內存屏障來實現,編譯器通過以下規則來進行實現的。
    • 使用volatile修飾的變量 **read(讀取),load(加載),use(使用)**都是連續出現的,所以每次使用變量時都要從主存讀取最新的變量值,替換私有內存的變量副本值
    • 對于同一個變量的**assign(賦值),store(存儲),write(主存)**操作都是連續出現,所以每次對變量的修改都會立即同步到主存中

?但是思考一下,單線程下**( read,load,use)(assign,store,write)**同時出現沒什么問題,但是在多線程并發執行的情況下,因為單個操作具備原子性,但是多個組合的話就不具備原子性了,還是有可能會出現臟數據。

下面通過圖來了解一下 并發時可能發生產生臟數據的場景

在這里插入圖片描述

對于復合操作,volatile變量是無法保證其原子性的,如果想要保證復合操作的原子性,那么就需要使用鎖,并在在高并發場景下,volatile變量一定要和Java顯示鎖結合使用

這里補充介紹一下 JMM內存模型的 8個 操作

操作描述作用的對象
read讀取把一個變量的值從主內存或高速緩存讀到線程的工作內存中,準備下一步的load操作
load加載入把read操作從主內存讀取的變量值放入線程的工作內存中的變量副本中,此時變量才對線程可見
use使用把工作內存中變量的值傳遞給執行引擎,作為運算的輸入
assign賦值把執行引擎計算出的結果賦值給工作內存中的變量
store存儲把工作內存中修改后的變量值寫回到主內存中
write寫出把store操作從工作內存中變量的值寫入到主內存,使得其他線程可見
lock加鎖作用于主內存的變量,標記變量為線程獨占,確保同一時刻只有一個線程能執行lock和unlock之間的操作
unlock解鎖釋放鎖,作用于主內存的變量,允許其他線程獲取該變量的鎖并進行操作

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

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

相關文章

用于生成 Avatar 的文本引導式情感和運動控制-InstructAvatar

網址 https://wangyuchi369.github.io/InstructAvatar/ 用于生成 Avatar 的文本引導式情感和運動控制 官網翻譯 最近的會說話的頭像生成模型在實現與音頻的真實和準確的嘴唇同步方面取得了長足的進步&#xff0c;但在控制和傳達頭像的詳細表情和情感方面往往存在不足&#…

APM2.8如何做加速度校準

加速度的校準建議準備一個六面平整&#xff0c;邊角整齊的方形硬紙盒或者塑料盒&#xff0c;如下圖所示&#xff0c;我們將以它作為APM校準時的水平垂直姿態參考&#xff0c;另外當然還需要一塊水平的桌面或者地面 首先用雙面泡沫膠或者螺絲將APM主板正面向上固定于方形盒子上&…

JavaScrip原型對象

參考 JavaScrip原型對象 | LogDicthttps://www.logdict.com/archives/javascripyuan-xing-mo-shi

每天寫兩道(二)LRU緩存、

146.LRU 緩存 . - 力扣&#xff08;LeetCode&#xff09; 請你設計并實現一個滿足 LRU (最近最少使用) 緩存 約束的數據結構。 實現 LRUCache 類&#xff1a; LRUCache(int capacity) 以 正整數 作為容量 capacity 初始化 LRU 緩存int get(int key) 如果關鍵字 key 存在于緩存…

如何使用Python和大模型進行數據分析和文本生成

如何使用Python和大模型進行數據分析和文本生成 Python語言以其簡潔和強大的特性&#xff0c;成為了數據科學、機器學習和人工智能開發的首選語言之一。隨著大模型&#xff08;Large Language Models, LLMs&#xff09;如GPT-4的崛起&#xff0c;我們能夠利用這些模型實現諸多…

Revit——(2)模型的編輯、軸網和標高

目錄 一、關閉縮小的隱藏窗口 二、標高&#xff08;可創建平面&#xff0c;其他標高線復制即可&#xff09; 三、軸網 周圍的四個圈和三角表示四個里面&#xff0c;可以移動&#xff0c;不要刪除 一、關閉縮小的隱藏窗口 二、標高&#xff08;可創建平面&#xff0c;其他標…

計算機體系結構期末快速復習

文章目錄 前言CPI&#xff0c;MIPS&#xff08;大題1&#xff09;加速比&#xff08;大題2&#xff09;流水線&#xff08;大題3&#xff09;CRAY-1向量機&#xff08;大題4&#xff09;Tomasulo算法&#xff08;大題5&#xff09;概念簡答題計算機系統結構的經典定義什么是透明…

深入分析 Android Activity (二)

文章目錄 深入分析 Android Activity (二)1. Activity 的啟動模式&#xff08;Launch Modes&#xff09;1.1 標準模式&#xff08;standard&#xff09;1.2 單頂模式&#xff08;singleTop&#xff09;1.3 單任務模式&#xff08;singleTask&#xff09;1.4 單實例模式&#xf…

利用邊緣計算網關的工業設備數據采集方案探討-天拓四方

隨著工業4.0時代的到來&#xff0c;工業設備數據采集成為了實現智能制造、提升生產效率的關鍵環節。傳統的數據采集方案往往依賴于中心化的數據處理方式&#xff0c;但這種方式在面對海量數據、實時性要求高的工業場景時&#xff0c;往往顯得力不從心。因此&#xff0c;利用邊緣…

CSS實現一個雨滴滑落效果

使用純CSS來實現一個真實的雨滴滑落效果可能會有些挑戰&#xff0c;因為CSS主要關注于靜態樣式和簡單的動畫效果。然而&#xff0c;你可以使用CSS動畫和keyframes來模擬一個雨滴滑落的簡化效果。 以下是一個基本的示例&#xff0c;展示如何使用CSS來模擬雨滴從頂部滑落到底部的…

AI學習指南數學工具篇-MATLAB中的凸優化工具

AI學習指南數學工具篇-MATLAB中的凸優化工具 在人工智能領域&#xff0c;凸優化是一個非常重要的數學工具&#xff0c;它在機器學習、深度學習、數據分析等領域都有著廣泛的應用。而MATLAB作為一款強大的數學工具軟件&#xff0c;提供了豐富的凸優化工具和函數&#xff0c;為用…

二叉樹的鏈式結構(二叉樹)與順序結構(堆)---數據結構

一、樹的概念與結構 1、樹的概念 樹是一種非線性的數據結構&#xff0c;它是由n&#xff08;n>0&#xff09;個有限結點組成一個具有層次關系的集合。我們常把它叫做樹&#xff0c;是因為它看起來像一棵倒掛的樹&#xff0c;它的根是朝上的&#xff0c;而葉是朝下的。 下面…

給我一個用斷言結果執行下一步的例子

在使用 pytest 和 Selenium 進行自動化測試時&#xff0c;通常我們會根據斷言的結果來決定測試流程的走向。如果斷言失敗&#xff0c;測試通常會停止執行后續的步驟&#xff0c;因為失敗意味著被測系統沒有按照預期工作。然而&#xff0c;有時候我們可能需要在斷言失敗后執行特…

每日復盤-20240528

今日重點關注&#xff1a; 20240528 六日漲幅最大: ------1--------300956--------- 英力股份 五日漲幅最大: ------1--------301361--------- 眾智科技 四日漲幅最大: ------1--------301361--------- 眾智科技 三日漲幅最大: ------1--------301361--------- 眾智科技 二日漲…

前端編程語言——JS背景知識、JS基礎語法、算數運算符和關系運算符(1)

0、前言&#xff1a; JS全稱是JavaScript&#xff0c;是一種腳本語言&#xff0c;誕生于1995年&#xff0c;JS是由ECMAScript&#xff08;包含js語法&#xff09;、BOM&#xff08;Brower Oject Model&#xff0c;和瀏覽器相關操作&#xff09;、DOM&#xff08;Document Obje…

ubuntu設置中文輸入法教程

在 Ubuntu 上設置中文輸入法可以通過以下步驟來完成。我們將以安裝和配置 fcitx 輸入法框架及其中文輸入法插件 fcitx-sunpinyin 為例。 ### 步驟一&#xff1a;安裝 fcitx 和中文輸入法插件 1. **更新軟件包列表** 打開終端并運行以下命令來更新軟件包列表&#xff1a; …

淺談—“文件映射”

目錄 文件映射頭文件&#xff1a; 核心函數 port flags 文件映射頭文件&#xff1a; #include<sys/mman.h> 核心函數 void *mmap(void *addr,size_t length, int port,int flags,int fd, off_t offset ); int munmap(void *addr,size_t length);// 對比free&#x…

聯邦和反射器實驗

拓撲圖 一.實驗要求 1.AS1存在兩個環回&#xff0c;一個地址為192.168.1.0/24&#xff0c;該地址不能在任何協議中宣告 AS3存在兩個環回&#xff0c;一個地址為192.168.2.0/24&#xff0c;該地址不能在任何協議中宣告 AS1還有一個環回地址為10.1.1.0/24&#xff…

PyTorch訓練關鍵點

1.背景 在網上找了一些資料用來訓練關鍵點&#xff0c;一般都是人臉或者車牌關鍵點訓練&#xff0c;或者是聯合檢測一起訓練。很少有是單獨基于輕量級網絡訓練單獨關鍵點模型的工程&#xff0c;本文簡單介紹一種簡單方法和代碼。 2.代碼模塊 &#xff08;1&#xff09;網絡結…

[C][動態內存分配][柔性數組]詳細講解

目錄 1.動態內存函數的介紹1.malloc2.free2.calloc4.realloc 2.常見的動態內存錯誤3.C/C程序的內存開辟4.柔性數組1.是什么&#xff1f;2.柔性數組的特點3.柔性數組的使用4.柔性數組的優勢 1.動態內存函數的介紹 1.malloc 函數原型&#xff1a;void* malloc(size_t size)功能…