Java花裏胡哨的內存模型

前言

由於Java內存模型,是(JVM內存結構、Java內存模型以及Java對象模型)知識點當中最晦澀難懂的一個,而且涉及到很多背景知識和相關知識。
在《深入理解Java虛擬機》和《Java併發編程的藝術》等書中也都有關於這個知識點的介紹。但是,自己讀完之後還是搞不清楚。就來整體的介紹一下Java內存模型
JMM規定Java線程間的通信採用共享內存的方式。在Java中,所有成員變量、靜態變量和數組元素都存儲在堆內存中,堆內存在線程之間共享,所以它們通常也稱爲共享變量。JMM定義了線程和主內存之間的抽
象關係:線程之間的共享變量存儲在主內存(Main Memory)中,每個線程都有一個私有的本地內存(Local
Memory,或者也可以稱爲工作內存 Work
Memory),本地內存中存儲了該線程以讀/寫共享變量的副本。本地內存是JMM的一個抽象概念,並不真實存在。它涵蓋了緩存、寫緩衝區、寄存器以及其他的硬件和編譯器優化。
摘抄於小黃人
在這裏插入圖片描述
當程序在運行過程中,會將運算需要的數據從主存複製一份到CPU的高速緩存當中,那麼CPU進行計算時就可以直接從它的高速緩存讀取數據和向其中寫入數據,當運算結束之後,再將高速緩存中的數據刷新到主存當中。

內存間交互協議

JMM規定了主內存和工作內存間具體的交互協議,即一個變量如何從主內存拷貝到工作內存、如何從工作內存同步到主內存之間的實現細節,這主要包含了下面8個步驟:
在這裏插入圖片描述

Java內存模型規定了所有的變量都存儲在主內存中,每條線程還有自己的工作內存,線程的工作內存中保存了該線程中是用到的變量的主內存副本拷貝,線程對變量的所有操作都必須在工作內存中進行,而不能直接讀寫主內存。不同的線程之間也無法直接訪問對方工作內存中的變量,線程間變量的傳遞均需要自己的工作內存和主存之間進行數據同步進行。

  • lock(鎖定):作用於主內存的X變量,把X變量標識爲一條線程獨佔狀態。

  • unlock(解鎖):作用於主內存X變量,把一個處於鎖定狀態的X變量釋放出來,釋放後的X變量纔可以被其他線程鎖定。

  • read(讀取):作用於主內存X變量,把X變量值從主內存傳輸到線程的工作內存中,以便隨後的load動作使用

  • load(載入):作用於工作內存的X變量,它把read操作從主內存中得到的X變量值放入工作內存的X變量副本中。

  • use(使用):作用於工作內存的X變量,把工作內存中的X變量值傳遞給執行引擎,每當虛擬機遇到一個需要使用X變量的值的字節碼指令時將會執行這個操作。

  • assign(賦值):作用於工作內存的X變量,它把一個從執行引擎接收到的值賦值給工作內存的X變量,每當虛擬機遇到一個給X變量賦值的字節碼指令時執行這個操作。

  • store(存儲):作用於工作內存的X變量,把工作內存中的X變量的值傳送到主內存中,以便隨後的write的操作。

  • write(寫入):作用於主內存的X變量,它把store操作從工作內存中X變量的值傳送到主內存的X變量中。

指令重排

在執行程序時,爲了提高性能,編譯器和處理器常常會對指令做重排序。從Java源代碼到最終實際執行的指令序列,會分別經歷下面3種重排序:

  • 編譯器優化的重排序。編譯器在不改變單線程程序語義的前提下,可以重新安排語句的執行順序。

  • 指令級並行的重排序。現代處理器採用了指令級並行技術(Instruction-LevelParallelism,ILP)來將多條指令重疊執行。如果不存在數據依賴性,處理器可以改變語句對應機器指令的執行順序。

  • 內存系統的重排序。由於處理器使用緩存和讀/寫緩衝區,這使得加載和存儲操作看上去可能是在亂序執行。

如果兩個操作訪問同一個變量,其中一個爲寫操作,此時這兩個操作之間存在數據依賴性。 編譯器和處理器不會改變存在數據依賴性關係的兩個操作的執行順序,即不會重排序。不管怎麼重排序,單線程下的執行結果不能被改變,編譯器、runtime和處理器都必須遵守as-if-serial語義。
JMM是一種規範,目的是解決由於多線程通過共享內存進行通信時,存在的本地內存數據不一致、編譯器會對代碼指令重排序、處理器會對代碼亂序執行等帶來的問題。

Java內存模型的實現

  1. 原子性
    爲了保證原子性,提供了兩個高級的字節碼指令monitorenter和monitorexit。在Java中對應的關鍵字就是synchronized
  2. 可見性
    Java內存模型是通過在變量修改後將新值同步回主內存,在變量讀取前從主內存刷新變量值的這種依賴主內存作爲傳遞媒介的方式來實現的。
    Java中的volatile關鍵字提供了一個功能,那就是被其修飾的變量在被修改後可以立即同步到主內存,被其修飾的變量在每次是用之前都從主內存刷新。因此,可以使用volatile來保證多線程操作時變量的可見性。
    除了volatile,Java中的synchronized和final兩個關鍵字也可以實現可見性。
  3. 有序性
    在Java中,可以使用synchronized和volatile來保證多線程之間操作的有序性。
    volatile關鍵字會禁止指令重排。synchronized關鍵字保證同一時刻只允許一條線程操作。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章