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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

Java内存模型详解

發布時間:2023/12/10 java 50 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java内存模型详解 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

  • 一、什么是JMM?為什么需要JMM?
  • 二、JMM 如何抽象線程和主內存之間的關系?
  • 三、Java 內存區域和 JMM 有何區別?
  • 四、happens-before原則
  • 五、總結

一、什么是JMM?為什么需要JMM?

Java 是最早嘗試提供內存模型的編程語言。

一般來說,編程語言也可以直接復用操作系統層面的內存模型。不過,不同的操作系統內存模型不同。如果直接復用操作系統層面的內存模型,就可能會導致同樣一套代碼換了一個操作系統就無法執行了。Java 語言是跨平臺的,它需要自己提供一套內存模型以屏蔽系統差異。

Java內存模型(JMM,Java Memory Model) ,是Java 定義的并發編程相關的一組規范,除了抽象了線程和主內存之間的關系之外,其還規定了從 Java 源代碼到 CPU 可執行指令的這個轉化過程要遵守哪些和并發相關的原則和規范,其主要目的是為了簡化多線程編程,增強程序可移植性的。

為什么要遵守這些并發相關的原則和規范呢? 這是因為并發編程下,像 CPU 多級緩存和指令重排這類設計可能會導致程序運行出現一些問題。為此,JMM 抽象了 happens-before 原則(后文會詳細介紹到)來解決這個指令重排序問題。

JMM 說白了就是定義了一些規范來解決這些問題,開發發者可以利用這些規范更方便地開發多線程程序。 對于 Java 開發者說,你不需要了解底層原理,直接使用并發相關的一些關鍵字和類(比如 volatile、synchronized、各種 Lock)即可開發出并發安全的程序。

二、JMM 如何抽象線程和主內存之間的關系?

首先介紹一下什么是主內存?什么是本地內存?

  • 主內存 : 所有線程創建的實例對象都存放在主內存中,不管該實例對象是成員變量還是方法中的本地變量(也稱局部變量)
  • 本地內存 : 每個線程都有一個私有的本地內存來存儲共享變量的副本,并且,每個線程只能訪問自己的本地內存,無法訪問其他線程的本地內存。本地內存是 JMM 抽象出來的一個概念,存儲了主內存中的共享變量副本。

Java 內存模型(JMM) 抽象了線程和主內存之間的關系,就比如說線程之間的共享變量必須存儲在主內存中。

Java 內存模型的抽象示意圖如下:

從上圖來看,線程 1 與線程 2 之間如果要進行通信的話,必須要經歷下面 2 個步驟:

線程 1 把本地內存中修改過的共享變量副本的值同步到主內存中去。
線程 2 到主存中讀取對應的共享變量的值。

也就是說,JMM 為共享變量提供了可見性的保障。

不過,多線程下,對主內存中的一個共享變量進行操作有可能誘發線程安全問題。舉個例子:

線程 1 和線程 2 分別對同一個共享變量進行操作,一個執行修改,一個執行讀取。
線程 2 讀取到的是線程 1 修改之前的值還是修改后的值并不確定,都有可能,因為線程 1 和線程 2 都是先將共享變量從主內存拷貝到對應線程的工作內存中。

關于主內存與工作內存直接的具體交互協議,即一個變量如何從主內存拷貝到工作內存,如何從工作內存同步到主內存之間的實現細節,Java 內存模型定義了以下八種同步操作:

  • 鎖定(lock): 作用于主內存中的變量,將他標記為一個線程獨享變量。
  • 解鎖(unlock): 作用于主內存中的變量,解除變量的鎖定狀態,被解除鎖定狀態的變量才能被其他線程鎖定。
  • read(讀取): 作用于主內存的變量,它把一個變量的值從主內存傳輸到線程的工作內存中,以便隨后的 load 動作使用。
  • load(載入): 把 read 操作從主內存中得到的變量值放入工作內存的變量的副本中。
  • use(使用): 把工作內存中的一個變量的值傳給執行引擎,每當虛擬機遇到一個使用到變量的指令時都會使用該指令。
  • assign(賦值): 作用于工作內存的變量,它把一個從執行引擎接收到的值賦給工作內存的變量,每當虛擬機遇到一個給變量賦值的字節碼指令時執行這個操作。
  • store(存儲): 作用于工作內存的變量,它把工作內存中一個變量的值傳送到主內存中,以便隨后的 write 操作使用。
  • write(寫入): 作用于主內存的變量,它把 store 操作從工作內存中得到的變量的值放入主內存的變量中。

除了這 8 種同步操作之外,還規定了下面這些同步規則來保證這些同步操作的正確執行:

  • 不允許一個線程無原因地(沒有發生過任何 assign 操作)把數據從線程的工作內存同步回主內存中。
  • 一個新的變量只能在主內存中 “誕生”,不允許在工作內存中直接使用一個未被初始化(load 或 assign)的變量,換句話說就是對一個變量實施 use 和 store 操作之前,必須先執行過了 assign 和 load 操作。
  • 一個變量在同一個時刻只允許一條線程對其進行 lock 操作,但 lock 操作可以被同一條線程重復執行多次,多次執行 lock 后,只有執行相同次數的 unlock 操作,變量才會被解鎖。
  • 如果對一個變量執行 lock 操作,將會清空工作內存中此變量的值,在執行引擎使用這個變量前,需要重新執行 load 或 assign 操作初始化變量的值。
  • 如果一個變量事先沒有被 lock 操作鎖定,則不允許對它執行 unlock 操作,也不允許去 unlock 一個被其他線程鎖定住的變量。

三、Java 內存區域和 JMM 有何區別?

這是一個比較常見的問題,很多初學者非常容易搞混。 Java 內存區域和內存模型是完全不一樣的兩個東西 :

  • JVM 內存結構和 Java 虛擬機的運行時區域相關,定義了 JVM 在運行時如何分區存儲程序數據。 就比如說堆主要用于存放對象實例。
  • Java 內存模型和 Java 的并發編程相關,抽象了線程和主內存之間的關系。 就比如說線程之間的共享變量必須存儲在主內存中,規定了從 Java 源代碼到 CPU 可執行指令的這個轉化過程要遵守哪些和并發相關的原則和規范,其主要目的是為了簡化多線程編程,增強程序可移植性的。

四、happens-before原則

前面說到JMM指定了一組排序規則,來保證線程之間的可見性。這一組規則即被稱為 happens-before

JMM規定,要想保證B操作能夠看到A操作的結果(無論他們是否在同一個線程),那么A和B之間必須滿足happens-before關系。

JSR 133 引入了 happens-before 這個概念來描述兩個操作之間的內存可見性。

為什么需要 happens-before 原則? happens-before 原則的誕生是為了程序員和編譯器、處理器之間的平衡。程序員追求的是易于理解和編程的強內存模型,遵守既定規則編碼即可。編譯器和處理器追求的是較少約束的弱內存模型,讓它們盡己所能地去優化性能,讓性能最大化。

happens-before 原則的設計思想:

  • 為了對編譯器和處理器的約束盡可能少,只要不改變程序的執行結果(單線程程序和正確執行的多線程程序),編譯器和處理器怎么進行重排序優化都行。
  • 對于會改變程序執行結果的重排序,JMM 要求編譯器和處理器必須禁止這種重排序。

JSR-133 對 happens-before 原則的定義:

  • 如果一個操作 happens-before 另一個操作,那么第一個操作的執行結果將對第二個操作可見,并且第一個操作的執行順序排在第二個操作之前。
  • 兩個操作之間存在 happens-before 關系,并不意味著 Java 平臺的具體實現必須要按照 happens-before 關系指定的順序來執行。如果重排序之后的執行結果,與按 happens-before 關系來執行的結果一致,那么 JMM 也允許這樣的重排序。

可以看下面這個例子:

int userNum = getUserNum(); // 1 int teacherNum = getTeacherNum(); // 2 int totalNum = userNum + teacherNum; // 3
  • 1 happens-before 2
  • 2 happens-before 3
  • 1 happens-before 3
    雖然 1 happens-before 2,但對 1 和 2 進行重排序不會影響代碼的執行結果,所以 JMM 是允許編譯器和處理器執行這種重排序的。但 1 和 2 必須是在 3 執行之前,也就是說 1,2 happens-before 3 。

happens-before 原則表達的意義其實并不是一個操作發生在另外一個操作的前面,雖然這從程序員的角度上來說也并無大礙。更準確地來說,它更想表達的意義是前一個操作的結果對于后一個操作是可見的,無論這兩個操作是否在同一個線程里。

舉個例子:操作 1 happens-before 操作 2,即使操作 1 和操作 2 不在同一個線程內,JMM 也會保證操作 1 的結果對操作 2 是可見的。

happens-before 常見規則有哪些?
happens-before 的規則有 8 條,重點講解下面列舉的 6 條:

  • 單線程規則。 一個線程中的每個動作都happens-before該線程中后續的每個動作。
  • 監視器鎖定規則。 監視器的解鎖動作happens-before后續對這個監視器的鎖定動作。
  • volatile變量規則。 對volatile字段的寫入動作happens-before后續對這個字段的讀取動作。
  • 線程start規則。 線程start()方法的執行happens-before一個啟動線程內的任意動作。
  • 線程join規則。 一個線程內的所有動作happens-before任意其他線程在該線程join()成功返回之前。
  • 傳遞規則。 如果A happens-before B,且B happens-beforeC,那么A happens-before C。

如果兩個操作不滿足上述任意一個 happens-before 規則,那么這兩個操作就沒有順序的保障,JVM 可以對這兩個操作進行重排序。

五、總結

  • Java 是最早嘗試提供內存模型的語言,其主要目的是為了簡化多線程編程,增強程序可移植性的。
  • CPU 可以通過制定緩存一致協議(比如 MESI 協議) 來解決內存緩存不一致性問題。
  • 為了提升執行速度/性能,計算機在執行程序代碼的時候,會對指令進行重排序。 簡單來說就是系統在執行代碼的時候并不一定是按照你寫的代碼的順序依次執行。指令重排序可以保證串行語義一致,但是沒有義務保證多線程間的語義也一致 ,所以在多線程下,指令重排序可能會導致一些問題。
  • JMM 除了抽象了線程和主內存之間的關系之外,其還規定了從 Java 源代碼到 CPU 可執行指令的這個轉化過程要遵守哪些和并發相關的原則和規范。
  • JSR 133 引入了 happens-before 這個概念來描述兩個操作之間的內存可見性。

參考鏈接:
https://javaguide.cn/java/concurrent/jmm.html#%E4%BB%8E-cpu-%E7%BC%93%E5%AD%98%E6%A8%A1%E5%9E%8B%E8%AF%B4%E8%B5%B7

總結

以上是生活随笔為你收集整理的Java内存模型详解的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。