0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內(nèi)不再提示

volatile的原理

科技綠洲 ? 來源:Java技術(shù)指北 ? 作者:Java技術(shù)指北 ? 2023-10-10 16:33 ? 次閱讀

今天來了解一下面試題:你對 volatile 了解多少。要了解 volatile 關鍵字,就得從 Java 內(nèi)存模型開始。最后到 volatile 的原理。

一、Java 內(nèi)存模型 (JMM)

大家都知道 Java 程序可以做到一次編寫然后到處運行。這個功勞要歸功于 Java 虛擬機。Java 虛擬機中定義了一種 Jva 內(nèi)存模型(JMM),用來屏蔽掉各種硬件和操作系統(tǒng)之間內(nèi)存訪問差異,讓 Java 程序可以在各個平臺中訪問變量達到相同的效果。

JMM 的主要目標是定義了程序中變量的訪問規(guī)則,就是內(nèi)存中存放和讀取變量的一些底層的細節(jié)。

JMM 規(guī)則

  1. 變量包含實例字段,靜態(tài)字段,構(gòu)成數(shù)組對象的元素,不包含局部變量和方法參數(shù)
  2. 變量都存儲在主內(nèi)存上。
  3. 每個線程在 CPU 中都有自己的 工作內(nèi)存 ,工作內(nèi)存保存了被該線程使用到的變量的主內(nèi)存副本拷貝。
  4. 線程對變量的所有操作都只能在工作內(nèi)存,不能直接讀寫主內(nèi)存的變量。
  5. 不同線程之間無法之間訪問對方工作內(nèi)存中的變量。

圖片

定義一個靜態(tài)變量: static int a = 1;

線程 1 工作內(nèi)存指向主內(nèi)存操作
----a = 1--
a = 1<--a = 1線程 1 拷貝主內(nèi)存變量副本
a = 3--a = 1線程 1 修改工作內(nèi)存變量值
a = 3-->a = 3線程 1 工作內(nèi)存變量存儲到主內(nèi)存變量

上面的一系列內(nèi)存操作,在 JMM 中定義了 8 種操作來完成。

JMM 交互

主內(nèi)存和工作內(nèi)存之間的交互,JMM 定義了 8 種操作來完成,每個操作都是原子性的。

  1. lock (鎖定): 作用于主內(nèi)存變量,把一個變量標識為一條內(nèi)存獨占的狀態(tài)。
  2. unlock (解鎖): 作用于主內(nèi)存變量,把 lock 狀態(tài)的變量釋放出來,釋放出來后才能被其他線程鎖定。
  3. read (讀取): 作用于主內(nèi)存變量,把一個變量的值從主內(nèi)存?zhèn)鬏數(shù)焦ぷ鲀?nèi)存中。
  4. load (載入): 作用于工作內(nèi)存變量,把 read 操作的變量放入到工作內(nèi)存副本中。
  5. use (使用): 作用于工作內(nèi)存變量,把工作內(nèi)存中的變量的值傳遞給執(zhí)行引擎,每當虛擬機遇到需要這個變量的值的字節(jié)碼指令時都執(zhí)行這個操作。
  6. assgin (賦值): 作用于工作內(nèi)存變量,把從執(zhí)行引擎收到的值賦值給工作內(nèi)存變量,每當虛擬機遇到需要賦值變量的值的字節(jié)碼指令時都執(zhí)行這個操作。
  7. store (存儲): 作用于工作內(nèi)存變量,把工作內(nèi)存中的一個變量值,傳送到主內(nèi)存。
  8. write (寫入): 作用于主內(nèi)存變量,把 store 操作的從工作內(nèi)存取到的變量寫入主內(nèi)存變量中。

圖片

從上圖中可知,JMM 交互在一條線程中是不會出現(xiàn)任何的問題。但是當有兩條線程的時候,線程 1 已經(jīng)修改了變量的值,但是并未刷新到主內(nèi)存時,如果此時線程 2 讀取變量得到的值并不是線程 1 修改過的數(shù)據(jù)。

當引入線程 2 的時候 定義一個靜態(tài)變量: static int a = 1;

操作順序線程 1 工作內(nèi)存線程 2 工作內(nèi)存指向主內(nèi)存操作
--------a = 1--
1a = 1--<--a = 1線程 1 拷貝主內(nèi)存變量副本
2a = 3----a = 1線程 1 修改工作內(nèi)存變量值
3a = 3---->a = 1線程 1 工作內(nèi)存變量存儲到主內(nèi)存變量,主內(nèi)存變量還未更新
4.1a = 3a = 1<--a = 3線程 2 拷貝主內(nèi)存變量副本隨后主內(nèi)存變量更新線程 1 工作內(nèi)存變量
4.2a = 3a = 1<--a = 3線程 1 工作內(nèi)存變量存儲到主內(nèi)存變量隨后線程 2 獲取主內(nèi)存變量副本

下面就可以用 volatile 關鍵字解決問題。

二、volatile

volatile 可以保證變量對所有線程可見,一條線程修改的值,其他線程對新值可以立即得知。還可以禁止指令的重排序。

可見性

修改內(nèi)存變量后立刻同步到主內(nèi)存中,其他的線程立刻得知得益于 Java 的先行發(fā)生原則

先行發(fā)生原則中的 volatile 原則:一個 volatile 變量的寫操作先行于后面發(fā)生的這個變量的讀操作

定義一個靜態(tài)變量: static int a = 1;

線程 1 工作內(nèi)存線程 2 工作內(nèi)存指向主內(nèi)存操作
------a = 1--
a = 1--<--a = 1線程 1 拷貝主內(nèi)存變量副本
a = 3----a = 1線程 1 修改工作內(nèi)存變量值
a = 3---->a = 1線程 1 工作內(nèi)存變量存儲到主內(nèi)存變量
a = 3a = 3<--a = 3volatile 原則: 主內(nèi)存變量保存線程A工作內(nèi)存變量操作在線程 2 工作內(nèi)存讀取主內(nèi)存變量操作之前

可見性原理

對 volatile 修飾的變量,在執(zhí)行寫操作的時候會多出一條 lock 前綴的指令。JVM 將 lock 前綴指令發(fā)送給 CPU ,CPU 處理寫操作后將最后的值立刻寫回主內(nèi)存,因為有 MESI 緩存一致性協(xié)議保證了各個 CPU 的緩存是一致的,所以各個 CPU 緩存都會對總線進行嗅探,本地緩存中的數(shù)據(jù)是否被別的線程修改了。

如果別的線程修改了共享變量的數(shù)據(jù),那么 CPU 就會將本地緩存的變量數(shù)據(jù)過期掉,然后這個 CPU 上執(zhí)行的線程在讀取共享變量的時候,就會從主內(nèi)存重新加載最新的數(shù)據(jù)。

原子性

volatile 并不保證變量具有原子性。

public class VolatileTest implements Runnable {

    public static volatile int num;

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            num++;
        }

    }

    public static void main(String[] args) {
        for(int i = 0; i < 100; i++) {
            VolatileTest t = new VolatileTest();
            Thread t0 = new Thread(t);
            t0.start();
        }
        System.out.println(num);
        
    }
}

這段代碼的結(jié)果有可能不是 100000,有可能小于 100000。因為 num++ 并不是原子性的。

有序性

volatile 是通過禁止指令重排序來保證有序性。為了優(yōu)化程序的執(zhí)行效率 JVM 在編譯 Java 代碼的時候或者 CPU 在執(zhí)行 JVM 字節(jié)碼的時候,不影響最終結(jié)果的前提下會對指令進行重新排序。

編譯器會根據(jù)以下策略將內(nèi)存屏障插入到指令中,禁止重排序:

  1. 在 volatile 寫操作之前插入 StoreStore 屏障。禁止和 StoreStore 屏障之前的普通寫操作不會進行重排序。
  2. 在 volatile 寫操作之后插入 StoreLoad 屏障。禁止和 StoreLoad 屏障之后的 volatile 讀寫重排序。
  3. 在 volatile 讀操作之后插入 LoadLoad 屏障。禁止和 LoadLoad 之后的普通讀和 volatile 讀重排序。
  4. 在 volatile 寫操作之后插入 LoadStore 屏障。禁止和 LoadStore 屏障之后的普通寫操作重排序。
聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學習之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 內(nèi)存
    +關注

    關注

    8

    文章

    3025

    瀏覽量

    74054
  • JAVA
    +關注

    關注

    19

    文章

    2967

    瀏覽量

    104758
  • 模型
    +關注

    關注

    1

    文章

    3243

    瀏覽量

    48842
  • volatile
    +關注

    關注

    0

    文章

    45

    瀏覽量

    13031
收藏 人收藏

    評論

    相關推薦

    什么是volatile

    00. 目錄文章目錄00. 目錄01. volatile概述02. volatile應用場景03. volatile應用示例04. 嵌入式系統(tǒng)中應用05. volatile官方說明
    發(fā)表于 10-28 09:23

    c語言volatile的作用

    volatile,則編譯器會逐一地進行編譯并產(chǎn)生相應的機器代碼(產(chǎn)生四條代碼)。volatile變量有兩個作用:一個是告訴編譯器不要進行優(yōu)化;另一個是告訴系統(tǒng)始終從內(nèi)存中取變量的地址,而不是從緩存中取變量的值(加volatile
    發(fā)表于 11-03 09:13 ?2409次閱讀
    c語言<b class='flag-5'>volatile</b>的作用

    Volatile與多線程的認識與理解

    volatile是一個類型修飾符(type specifier),就像大家更熟悉的const一樣,它是被設計用來修飾被不同線程訪問和修改的變量。volatile的作用是作為指令關鍵字,確保本條指令
    發(fā)表于 12-01 10:31 ?1658次閱讀

    java之用volatile和不用volatile的區(qū)別

    volatile是一個類型修飾符(type specifier),就像大家更熟悉的const一樣,它是被設計用來修飾被不同線程訪問和修改的變量。Java具有簡單性、面向?qū)ο?、分布式、健壯性、安全性、平臺獨立與可移植性、多線程、動態(tài)性等特點。
    發(fā)表于 12-01 10:52 ?3486次閱讀

    volatile修飾的變量的認識和理解

     談到volatile,理解原子性和易變性是不同的概念這一點很重要,volatile是輕量級的鎖,它只具備可見性,但沒有原子特性。如果你將一個域聲明為volatile,那么只要對這個域產(chǎn)生了寫操作
    發(fā)表于 12-01 11:36 ?5725次閱讀
    <b class='flag-5'>volatile</b>修飾的變量的認識和理解

    Java中volatile的作用以及用法

    Java 語言中的 volatile 變量可以被看作是一種 “程度較輕的 synchronized”;與 synchronized 塊相比,volatile 變量所需的編碼較少,并且運行時開銷也較少,但是它所能實現(xiàn)的功能也僅是 synchronized 的一部分。
    發(fā)表于 12-01 12:14 ?7012次閱讀

    volatile變量定義的意義和該用在哪里

    volatile 影響編譯器編譯的結(jié)果,volatile指出 變量是隨時可能發(fā)生變化的,與volatile變量有關的運算,不要進行編譯優(yōu)化,以免出錯
    發(fā)表于 03-07 15:29 ?3674次閱讀
    <b class='flag-5'>volatile</b>變量定義的意義和該用在哪里

    C語言類型修飾符Volatile的使用說明

    C語言是我們經(jīng)常需要用到的語言,C語言中的類型修飾符Volatile大家知道怎么使用嗎? volatile是一個類型修飾符(type specifier).volatile的作用是作為指令關鍵字
    的頭像 發(fā)表于 09-19 10:54 ?3552次閱讀

    volatile有哪些使用誤區(qū)

    在建立編譯環(huán)境的時候用typedef定義了指向volatile 單元的指針,最后終于發(fā)現(xiàn)行不通。
    發(fā)表于 08-06 17:34 ?0次下載
    <b class='flag-5'>volatile</b>有哪些使用誤區(qū)

    如何使用C++語法中的volatile

    volatile volatile int i = 10; volatile 關鍵字是一種類型修飾符,用它聲明的類型變量表示可以被某些編譯器未知的因素(操作系統(tǒng)、硬件、其它線程等)更改。所以
    的頭像 發(fā)表于 09-09 09:38 ?1490次閱讀

    C++基礎語法之volatile、assert()和sizeof()

    volatile volatile int i = 10; volatile 關鍵字是一種類型修飾符,用它聲明的類型變量表示可以被某些編譯器未知的因素(操作系統(tǒng)、硬件、其它線程等)更改。所以
    的頭像 發(fā)表于 09-09 09:48 ?1313次閱讀

    【嵌入式】C語言中volatile關鍵字

    00. 目錄文章目錄00. 目錄01. volatile概述02. volatile應用場景03. volatile應用示例04. 嵌入式系統(tǒng)中應用05. volatile官方說明
    發(fā)表于 10-21 10:21 ?6次下載
    【嵌入式】C語言中<b class='flag-5'>volatile</b>關鍵字

    C語言中的volatile是什么

    學C語言時有一個奇怪的關鍵字volatile,這到底有什么用呢?
    的頭像 發(fā)表于 02-17 14:29 ?1227次閱讀
    C語言中的<b class='flag-5'>volatile</b>是什么

    volatile的實現(xiàn)原理分析

    `volatile`是一個輕量級的`synchronized`,一般作用于 **變量** ,在多處理器開發(fā)的過程中保證了內(nèi)存的可見性。相比于`synchronized`關鍵字,`volatile`關鍵字的執(zhí)行成本更低,效率更高
    的頭像 發(fā)表于 05-11 17:33 ?639次閱讀
    <b class='flag-5'>volatile</b>的實現(xiàn)原理分析

    介紹下volatile的底層原理

    線程安全的三大特性,原子性、可見性、有序性,這三大特性與我們之前整理的內(nèi)容息息相關。本篇重點介紹下volatile的底層原理,幫助我們更好的理解java并發(fā)包。
    的頭像 發(fā)表于 06-09 16:17 ?850次閱讀
    介紹下<b class='flag-5'>volatile</b>的底層原理