日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > java >内容正文

java

高并发编程-通过volatile重新认识CPU缓存 和 Java内存模型(JMM)

發(fā)布時(shí)間:2025/3/21 java 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 高并发编程-通过volatile重新认识CPU缓存 和 Java内存模型(JMM) 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

文章目錄

  • 概述
  • volatile定義
  • CPU緩存
    • 相關(guān)CPU術(shù)語
    • CPU緩存一致性協(xié)議MESI
    • 帶有高速緩存的CPU執(zhí)行計(jì)算的流程
    • CPU 多級(jí)的緩存結(jié)構(gòu)
  • Java 內(nèi)存模型 (JMM)
    • 線程通信的兩種方式
    • 哪些變量可以共享
    • JMM概述
    • Java內(nèi)存模型的抽象結(jié)構(gòu)示意圖
  • volatile 小demo
  • 總結(jié):volatile的兩條實(shí)現(xiàn)原則

概述

在多線程并發(fā)編程中synchronized和volatile都扮演著重要的角色。 volatile是輕量級(jí)的 synchronized,它在高并發(fā)中保證了共享變量的“可見性”。

那什么是可見性呢?

可見性 我們可以理解為:當(dāng)一個(gè)線修改一個(gè)共享變量時(shí),另外一個(gè)線程能讀到這個(gè)修改的值。

如果volatile變量修飾符使用恰的話,它比synchronized的使用和執(zhí)行成本更低,因?yàn)?font color="red">volatile不會(huì)引起線程上下文的切換和調(diào)度


volatile定義

Java規(guī)范第3版中對volatile的定義如下:Java允許線程訪問共享變量,為了確保共享變量能被準(zhǔn)確和一致地更新,線程應(yīng)該確保通過排他鎖單獨(dú)獲得這個(gè)變量。

Java提供了volatile關(guān)鍵字,在某些場景下volatile比鎖synchronized要更加方便。如果一個(gè)字段被聲明成volatile,Java線程內(nèi)存模型(JMM)確保所有線程看到這個(gè)變量的值是一致的 .


CPU緩存

相關(guān)CPU術(shù)語

了解volatile實(shí)現(xiàn)原理之前,先了解下與其實(shí)現(xiàn)原理相關(guān)的CPU術(shù)語

術(shù)語英文說明
內(nèi)存屏障memory barriers一組處理器指令,用于實(shí)現(xiàn)對內(nèi)存操作的順序限制
緩沖行cache line緩存中可以分配的最小存儲(chǔ)單位。處理器填寫緩存線時(shí)會(huì)加載整個(gè)緩存線,需要使用多個(gè)主內(nèi)存讀周期;
原子操作atomic operations不可中斷的一個(gè)或一系列的操作
緩存行填充cache line fill當(dāng)處理器識(shí)別到從內(nèi)存中讀取操作數(shù)是可緩存的,處理器讀取整個(gè)緩存行到適當(dāng)?shù)木彺?#xff1b;
緩存命中cache hit如果進(jìn)行高速緩存行填充操作的內(nèi)存位置仍然是下次處理器訪問的地址時(shí),處理器從緩存中讀取操作數(shù),而不是從內(nèi)存中讀取;
寫命中write hit當(dāng)處理器操作數(shù)寫回到一個(gè)內(nèi)存緩存的區(qū)域時(shí),它首先會(huì)檢查這個(gè)緩存的內(nèi)存地址是否在緩存行中,如果存在一個(gè)有效的緩存行,則處理器將這個(gè)操作數(shù)寫回到緩存,而不是寫回到內(nèi)存,這個(gè)操作被稱為寫命中;
寫缺失write miss the cache一個(gè)有效的緩存行被寫入到不存在的內(nèi)存區(qū)域。

CPU緩存一致性協(xié)議MESI

CPU緩存一致性協(xié)議MESI 請參考: CPU緩存一致性協(xié)議MESI

【M 修改 (Modified) E 獨(dú)享、互斥 (Exclusive) S 共享 (Shared) I 無效 (Invalid) 】


CPU的發(fā)展速度非常快,而內(nèi)存和硬盤的發(fā)展速度遠(yuǎn)遠(yuǎn)不及CPU。這就造成了高性能能的內(nèi)存和硬盤價(jià)格及其昂貴。然而CPU的高度運(yùn)算需要高速的數(shù)據(jù)。為了解決這個(gè)問題,CPU廠商在CPU中內(nèi)置了少量的高速緩存以解決I\O速度和CPU運(yùn)算速度之間的不匹配問題

為了提高效率,CPU不直接和內(nèi)存進(jìn)行通信,而是先將系統(tǒng)內(nèi)存的數(shù)據(jù)讀取到內(nèi)部緩存(L1、L2或其他)后再進(jìn)行操作。

但是有個(gè)問題: 當(dāng)操作完成后,被修改的數(shù)據(jù)何時(shí)回寫到主內(nèi)存呢?

假設(shè)某個(gè)共享變量聲明了volatile關(guān)鍵字進(jìn)行寫操作 ,JVM就會(huì)向處理器發(fā)送一條Lock前綴指令,將這個(gè)變量所在緩存行的數(shù)據(jù)寫回到系統(tǒng)內(nèi)存。

OK,就算寫回到內(nèi)存,如果其他處理器緩存的值還是舊的,再執(zhí)行計(jì)算操作就會(huì)有問題。

所以,在多處理器下,為了保證各個(gè)處理器的緩存是一致的,就要實(shí)現(xiàn)緩存一致性協(xié)議 ,每個(gè)處理器通過嗅探在總線(BUS)上傳播的數(shù)據(jù)來檢查自己緩存的值是不是過期了

  • 當(dāng)處理器發(fā)現(xiàn)自己緩存行對應(yīng)的內(nèi)存地址被修改,就會(huì)將當(dāng)前處理器的緩存行設(shè)置成無效狀態(tài),
  • 當(dāng)處理器對這個(gè)數(shù)據(jù)進(jìn)行修改操作的時(shí)候,會(huì)重新從系統(tǒng)內(nèi)存中把數(shù)據(jù)讀到處理器緩存

帶有高速緩存的CPU執(zhí)行計(jì)算的流程

  • 程序以及數(shù)據(jù)被加載到主內(nèi)存
  • 指令和數(shù)據(jù)被加載到CPU的高速緩存
  • CPU執(zhí)行指令,把結(jié)果寫到高速緩存
  • 高速緩存中的數(shù)據(jù)寫回主內(nèi)存

  • CPU 多級(jí)的緩存結(jié)構(gòu)

    由于CPU的運(yùn)算速度超越了1級(jí)緩存的數(shù)據(jù)I\O能力,CPU廠商又引入了多級(jí)的緩存結(jié)構(gòu)。

    L1/L2/L3 Cache速度差別

    L1 cache: 3 cycles

    L2 cache: 11 cycles

    L3 cache: 25 cycles

    Main Memory: 100 cycles

    通常L1 Cache離CPU核心需要數(shù)據(jù)的地方更近,而L2 Cache則處于邊緩位置,訪問數(shù)據(jù)時(shí),L2 Cache需要通過更遠(yuǎn)的銅線,甚至更多的電路,從而增加了延時(shí)。

    參見: 細(xì)說Cache-L1/L2/L3/TLB


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

    線程通信的兩種方式

    我們知道 線程間的通信,主要分為兩種方式

  • 共享內(nèi)存 (線程之間共享程序的公共狀態(tài),通過寫-讀內(nèi)存中的公共狀態(tài)進(jìn)行隱式通信)
  • 消息傳遞 (線程之間必須通過發(fā)送消息來顯式進(jìn)行通信)

  • 哪些變量可以共享

    Java的并發(fā)采用的是共享內(nèi)存模型 , 在Java中,所有實(shí)例域、靜態(tài)域和數(shù)組元素都存儲(chǔ)在堆內(nèi)存中,堆內(nèi)存在線程之間共享 ,我們使用”共享變量”這個(gè)術(shù)語代指實(shí)例域,靜態(tài)域和數(shù)組元素

    局部變量,方法定義參數(shù)和異常處理器參數(shù)不會(huì)在線程之間共享,它們不會(huì)有內(nèi)存可見性問題,也不受內(nèi)存模型的影響。


    JMM概述

    Java線程之間的通信由Java內(nèi)存模型JMM)控制,JMM決定一個(gè)線程對共享變量的寫入何時(shí)對另一個(gè)線程可見 .

    JMM定義了線程和主內(nèi)存之間抽象關(guān)系:線程之間的共享變量存儲(chǔ)在主內(nèi)存(Main Memory)中,每個(gè)線程都有一個(gè)私有的本地內(nèi)存(Local Memory),本地內(nèi)存中存儲(chǔ)了該線程以讀/寫共享變量的副本

    注: 本地內(nèi)存是JMM的一個(gè)抽象概念,并不真實(shí)存在。它涵蓋了緩存、寫緩沖區(qū)、寄存器以及其他的硬件和編譯器化


    Java內(nèi)存模型的抽象結(jié)構(gòu)示意圖

    如下:

    根據(jù)上述的描述,如果線程A和線程B要通信的話,步驟如下

  • 線程A把本地內(nèi)存A中更新過的共享變量刷新到主內(nèi)存中去
  • 線程B到主內(nèi)存中去讀取線程A之前已更新過的共享變
  • 線程A和線程B通信示意圖如下所示

    本地內(nèi)存A和本地內(nèi)存B由主內(nèi)存中共享變量x的副本。

  • 假設(shè)初始時(shí),這3個(gè)內(nèi)存(本地內(nèi)存A、本地內(nèi)存B、主內(nèi)存)中的x值都為0。
  • 線程A在執(zhí)行時(shí),把更新后的x值(假設(shè)值為1)臨時(shí)存放在自己的本地內(nèi)A中。
  • 當(dāng)線程A和線程B需要通信時(shí),線程A首先會(huì)把自己本地內(nèi)存中修改后的x值刷新到主內(nèi)存中,此時(shí)主內(nèi)存中的x值變?yōu)榱?。
  • 隨后,線程B到主內(nèi)存中去讀取線程A更新后的x值,此時(shí)線程B的本地內(nèi)存的x值也變?yōu)榱?。
  • 從整體來看,這兩個(gè)步驟實(shí)質(zhì)上是線程A在向線程B發(fā)送消息,而且這個(gè)通信過程必須要經(jīng)過主內(nèi)存。JMM通過控制主內(nèi)存與每個(gè)線程的本地內(nèi)存之間的交互,來保證內(nèi)存可見性保證


    volatile 小demo

    先來個(gè)例子 感受下volatile的作用

    倆線程 1個(gè)讀取共享變量 另外1個(gè)更新共享變量.

    package com.artisan.test;/*** 倆線程* <p>* 1個(gè)讀取共享變量* 1個(gè)更新共享變量*/ public class VolatileDemo {// 共享變量private volatile static int SHARED_VALUE = 0;private final static int MAX_VALUE = 10;public static void main(String[] args) {// 定義 讀取線程new Thread(() -> {int localValue = SHARED_VALUE;// 循環(huán), 如果localValue != SHARED_VALUE 輸出信息while (SHARED_VALUE < MAX_VALUE){if (localValue != SHARED_VALUE){System.out.printf(Thread.currentThread().getName() + ": the SHARED_VALUE value has been updated to [%d] \n" , SHARED_VALUE);localValue = SHARED_VALUE;}}}, "Reader Thread").start();// 定義 更新線程new Thread(() -> {int localValue = SHARED_VALUE;// 循環(huán) 如果小于最大值,則更新localValuewhile (SHARED_VALUE < MAX_VALUE){System.out.printf(Thread.currentThread().getName() + ": update SHARED_VALUE to [%d] \n" , ++localValue);SHARED_VALUE = localValue;try {// 為了演示效果,休眠一下Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}}}, "Update Thread").start();} }

    輸出:

    Update Thread: update SHARED_VALUE to [1] Reader Thread: the SHARED_VALUE value has been updated to [1] Update Thread: update SHARED_VALUE to [2] Reader Thread: the SHARED_VALUE value has been updated to [2] Update Thread: update SHARED_VALUE to [3] Reader Thread: the SHARED_VALUE value has been updated to [3] Update Thread: update SHARED_VALUE to [4] Reader Thread: the SHARED_VALUE value has been updated to [4] Update Thread: update SHARED_VALUE to [5] Reader Thread: the SHARED_VALUE value has been updated to [5] Update Thread: update SHARED_VALUE to [6] Reader Thread: the SHARED_VALUE value has been updated to [6] Update Thread: update SHARED_VALUE to [7] Reader Thread: the SHARED_VALUE value has been updated to [7] Update Thread: update SHARED_VALUE to [8] Reader Thread: the SHARED_VALUE value has been updated to [8] Update Thread: update SHARED_VALUE to [9] Reader Thread: the SHARED_VALUE value has been updated to [9] Update Thread: update SHARED_VALUE to [10] Process finished with exit code 0

    如果 去掉volatile關(guān)鍵字呢 ? 測試如下

    由此可見 volatile關(guān)鍵字在高并發(fā)中保證了共享變量的“可見性”。


    總結(jié):volatile的兩條實(shí)現(xiàn)原則

    總結(jié)一下

    • Lock前綴指令會(huì)引起處理器緩存回寫到內(nèi)存
    • 一個(gè)處理器的緩存回寫到內(nèi)存會(huì)導(dǎo)致其他處理器的緩存無效

    總結(jié)

    以上是生活随笔為你收集整理的高并发编程-通过volatile重新认识CPU缓存 和 Java内存模型(JMM)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。