java內(nèi)存模型

1.內(nèi)存模型(JMM)

1.1什么是Java內(nèi)存模型?

Java內(nèi)存模型將內(nèi)存分為主內(nèi)存和工作內(nèi)存兩大部分;主內(nèi)存用來存儲線程之間共享數(shù)據(jù),工作內(nèi)存則是每個線程獨享內(nèi)存,存儲對應線程使用到的共享數(shù)據(jù)副本

java內(nèi)存模型JMM.png

Java內(nèi)存模型和硬件中的多核CPU、CPU對應的緩存(1級緩存、2級緩存、3級緩存)、以及內(nèi)存之間的設計異曲同工;CPU的每個核可以跑不通的線程,由于CPU執(zhí)行指令速度遠遠大于內(nèi)存讀寫速度,為了平衡兩者差距,高速緩存就引入了計算機體系中,高速緩存位于CPU和內(nèi)存之間;讀寫速度能力 CPU>L1>L2>L3>Memory(越靠近CPU側(cè),處理能力越高,讀寫越快);存儲數(shù)據(jù)能力Memory>L3>L2>L1>CPU(越靠近Memory側(cè),存儲能力越強);計算機執(zhí)行程序時,CPU執(zhí)行程序時,會頻繁的使用數(shù)據(jù)從內(nèi)存中拷貝到緩存中,這樣CPU運算時可以更快的讀取到運算的數(shù)據(jù);當CPU運算結束,又將數(shù)據(jù)從高速緩存寫回到內(nèi)存中。

1.2.內(nèi)存模型中工作內(nèi)存和主內(nèi)存的區(qū)別?
  • 主內(nèi)存:所有線程共享的區(qū)域;存儲共享數(shù)據(jù)的區(qū)域
  • 工作內(nèi)存:線程私有的存儲區(qū)域;主要存儲線程使用的主內(nèi)存中數(shù)據(jù)的副本
1.3.內(nèi)存模型帶來什么問題

工作內(nèi)存和主內(nèi)存中數(shù)據(jù)不一致問題

  1. 原子性

    線程1 和 線程2 并發(fā)執(zhí)行 +1操作,并同步到主內(nèi)存中

內(nèi)存模型-原子性.png
  1. 可見性

    內(nèi)存模型-可見性.png
  1. 重排序
public class Test{
  private int a = 0;
  private int b = 0;
  
  public void setAAndB() {
    a = 1;
    b = 2;
  }
  
  public void changeB() {
    if (a == 1) {
      b = 4;
    }
    System.out.println("b=" + b);
  }
}
內(nèi)存模型重排序.png
1.4.volatile關鍵字有哪些語義

volatile主要面對JMM帶來的問題(工作內(nèi)存和主內(nèi)存中數(shù)據(jù)不一致)而產(chǎn)生的一種解決方案

語義有

  1. volatile修飾的變量具有可見性
  2. volatile修飾的變量讀寫原子性(如32位系統(tǒng)上的Long變量)
  3. volatile修飾的變量,讀寫操作不會參與前后指令的重排序

具體理解

  1. volatile修飾的變量具有可見性

    對于一個volatile變量的讀,總能看到(任意線程)對這個volatile變量最后的寫入值

    寫:當寫一個volatile變量時,JMM會把該線程對應的本地內(nèi)存中的共享變量值刷新到主內(nèi)存中

    讀:當讀一個volatile變量時,JMM會把該線程對應的本地內(nèi)存置為無效,重新從主內(nèi)存中讀取共享變量到工作內(nèi)存中

    volatile可見性.png
  1. volatile修飾的變量讀寫原子性(單步操作)

    對于任意單個volatile變量的讀、寫具有原子性;但是對于i++這種復合操作不具有原子性

  2. volatile修飾的變量,讀寫操作不會參與前后指令的重排序

    是否可以重排序 第二個操作 第二個操作 第二個操作
    第一個操作 普通讀/寫 volatile讀 volatile寫
    普通讀/寫 不可以重排序
    volatile讀 不可以重排序 不可以重排序 不可以重排序
    volatile寫 不可以重排序 不可以重排序

    總結:

    • 第一個操作為volatile讀時,不管第二個操作是什么,都不可以重排序
    • 第二個操作為volatile寫時,不管第一個操作是什么,都不可以重排序
    • 第一個操作為volatile寫,第二個操作為volatile讀時,不可以重排序
1.5.volatile是怎么實現(xiàn)的

還是圍繞三個語義來看

  1. 可見性

    在寫入volatile變量的時候,JVM會向CPU發(fā)送Lock指令,Lock指令具有兩個作用

    • 將當前工作內(nèi)存中數(shù)據(jù)寫入到主內(nèi)存中
    • 涉及其他線程對應的CPU核中緩存的數(shù)據(jù)設置無效(CPU消息一致性協(xié)議和嗅探功能),之后讀會重新重主內(nèi)存拉取最新數(shù)據(jù)
  2. 原子性(讀寫原子性)

    線程是CPU調(diào)度的基本單位。CPU有時間片的概念,會根據(jù)不同的調(diào)度算法進行線程調(diào)度。當一個線程獲得時間片之后開始執(zhí)行,在時間片耗盡之后,就會失去CPU使用權。所以在多線程場景下,由于時間片在線程間輪換,就會發(fā)生原子性問題。

    為了保證原子性,需要通過字節(jié)碼指令monitorenter和monitorexit,但是volatile和這兩個指令之間是沒有任何關系的。

    所以,volatile是不能保證(復合操作)原子性的

  3. 重排序(有序性)

    主要靠內(nèi)存屏障(硬件層面的指令,它可以禁止屏障前后的指令進行重排序)

    • 在每個volatile寫操作的前面插入一個StoreStore屏障
    • 在每個volatile寫操作的后面插入一個StoreLoad屏障
    • 在每個volatile讀操作的后面插入一個LoadLoad屏障
    • 在每個volatile讀操作的后面插入一個LoadStore屏障
    volatile-重排序機制.png
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容