JVM——Java內存模型

重點

內存可見性、重排序、順序一致性、volatile、鎖、final

主內存和工作內存

Java內存模型主要目標用來屏蔽掉各種硬件和操作系統的內存之間的差異,以實現Java程序在各個平臺下都能達到一致的n內存訪問效果。Java內存模型定義了程序中變量的訪問規則,即虛擬機將變量存儲到內存和從內存取出比變量的規則。此處的變量是廣義的,包括實例、靜態變量或者數組元素,但是局部變量和方法參數是線程私有的,不包含這兩個,和前邊的提到變量區分開。
主內存可以理解爲物理硬件的主內存,工作內存是每個線程私有的內存,可以類比成處理器的高速緩存。Java內存模型規定了所有的變量都存儲在主內存中,線程的工作內存保存了被該線程使用的變量在主內存中的拷貝,規定線程對變量所有的操作(讀取、賦值)都在自己的工作內存中完成,各個線程的工作內存不能相互獨立 ,而線程之間通過主內存完成變量值的傳遞。Java線程、工作內存和主內存之間關係可以由下圖表示:
(圖片來源:https://www.cnblogs.com/nexiyi/p/java_memory_model_and_thread.html)
在這裏插入圖片描述

內存之間的交互操作

主內存和工作內存的交互協議,Java內存模型規定了8種操作完成,每種操作都是原子的、不可再分的(long和double在某些平臺允許有例外,後邊會提到):

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

  • unlock(解鎖):作用於主內存的變量,將一個鎖定的變量解鎖釋放,解鎖釋放後的變量纔可以被重新鎖定。

  • read(讀取):作用於工作內存的變量,將一個變量從主內存中傳輸到線程的工作內存中去,以便於後續的load操作使用。

  • load(載入):作用於工作內存的變量,將read操作讀取過來的變量存儲到工作內存中的變量副本中。

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

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

  • store(存儲):作用於工作內存變量。將工作內存中的變量傳遞到主內存中,以便於後邊的write操作使用。

  • write(寫入):作用主內存的變量,將store操作傳遞來變量值保存到主內存中的變量中。
    如果要把主內存中的變量存儲到工作內存中,就要順序的執行read和load操作,如果要把工作內存中的變量存儲到主內存中,就要順序的執行store和write操作。Java內存模型規定了這兩組操作的執行順序,但是不要求是連續執行的。除此之外,Java內存模型還有如下8種規定:

  • 不允許read和load、store和write操作單獨出現,即read和load要麼都有,要麼都沒有。

  • 不允許一個線程丟棄它最近的assign操作,即變量被賦值之後必須同步到主內存中去。

  • 不允許一個線程沒有assign操作就把變量數據同步到主內存中去。

  • 一個新的變量只能誕生於主內存中,即工作內存中不能直接使用一個未被初始化的變量(沒有load或者assign)

  • 一個變量,同一時刻,只能被一個線程做lock操作,但是一個變量可以被同一個線程多次lock,多次lock之後的變量要被同一線程執行相同次數的unlock操作纔可以被解鎖。

  • 如果一個變量被lock後,線程中的該變量的值會被清空,如果執行引擎要使用這個變量,必須重新load或者assign。

  • 如果一個變量沒有被lock,那麼就不允許對這個變量執行unlock操作,被lock鎖定的變量不能其他線程unlock。

  • 一個變量unlock之前,必須將此變量同步到主內存中去。

對於volatile型變量的特殊規則

關鍵字volatile是Java虛擬機提供的最輕量級的同步機制,Java內存模型對volatile變量提供了特殊的訪問規則。當一個變量被定義成volatile類型之後,具備兩種特性:
特性一:保證此變量對其他所有線程的可見性
可見性是指,當一個線程對此變量進行了修改,那麼新的值對於其他的線程來說是立即可知的。和普通變量的區別就是,普通變量在線程之前的傳遞都是通過主內存來完成的。volatile關鍵字並不會保證一個變量在所有的線程中一致,換句話說,volatile變量不存在一致性這個說法,可能一致,也可能不一致。執行引擎使用volatile變量之前都會刷新變量,所以對於執行引擎來說,它看不到不一致的情況,所以“volatile變量的運算在併發下是安全的”這種說法是錯誤的,運算操作並不是原子性的,可能運算之間,其他線程對變量進行了修改,導致併發問題。
特性二:禁止指令重排序優化
和普通變量的區別:普通變量僅僅保證在方法執行的過程中,所有依賴複製結果的地方都能夠獲取到爭取的結果,但是不能保證變量賦值的操作順序和代碼中的執行順序一致。比如說,指令1把地址A中的值加10,指令2把地址A的值乘以2,執行3把地址B中的值減去3,這時候,指令1和指令2是有依賴關係的,指令1和指令2是不能重排序的,因爲(A+10)2不等於A2+10,但是指令3和A沒有關係,可以在指令1和指令2之前或者之間執行。

對於long和double類型的特殊規則

Java內存模型要求內存之間的交互的8個操作都是原子性的。但是對於64位類型(long和double),Java模型允許將沒有 volatile修飾的64位類型數據的讀寫分爲2次32位操作,即所謂的“非原子性協定”。如果多個線程共享一個未被volatile修飾的64位變量,並且對其做讀寫操作,那麼某些線程可能會得到一個既非原值也非其他線程修改的值這種“半個變量”,但是這種情況非常罕見,起碼商用虛擬機不會出現這種情況,所以在編寫代碼時一般不需要將long和double類型用volatile來修飾。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章