您好,登錄后才能下訂單哦!
這篇文章主要介紹“Java內存模型的工作模式是什么”,在日常操作中,相信很多人在Java內存模型的工作模式是什么問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Java內存模型的工作模式是什么”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
現代計算機中,CPU的指令速度遠超內存的存取速度,由于CPU和內存的運算速度有幾個數量級的差距,所以現代計算機系統加入一層讀寫速度盡可能接近CPU運算速度的高速緩存(Cache)來作為內存與處理器之間的緩沖,將運算需要使用到的數據復制到緩存中,CPU運算操作的是內存數據的副本,當運算結束后再從緩存將副本數據同步回內存之中,這樣處理器就無須等待緩慢的內存讀寫了。
當CPU要讀取一個數據時,首先從一級緩存中查找,如果沒有找到再從二級緩存中查找,如果還是沒有就從三級緩存或內存中查找。
計算機在運行中,先從內存中取出第一條指令,通過控制器譯碼,按照指令的要求從存儲器中取出數據進行制定的邏輯運算后再按照內存地址把結果寫回內存中,接著再取出來第二條指令,依次遍歷執行下去。
單線程:CPU核心緩存只被一個線程訪問。緩存獨占,不會出現訪問沖突問題。
單核 CPU,多線程:進程中的多個線程會同時訪問進程中的共享數據,CPU 將某塊內存加載到緩存后,不同線程在訪問相同的物理地址的時候,都會映射到相同的緩存位置,這樣即使發生線程的切換,緩存仍然不會失效,何時候只有一個線程在執行,不會發生訪問沖突的問題。
多核 CPU,多線程:每個核都至少有一個 L1 緩存。多個線程訪問進程中的某個共享內存,且這多個線程分別在不同的核心上執行,則每個核心都會在各自的Cache中保留一份共享內存的緩沖,由于多核是可以并行的,可能會出現多個線程同時寫各自的緩存的情況,而各自的 Cache 之間的數據就有不一致性的問題(并發導致的根源問題)。
為了解決這個問題,需要各個處理器訪問緩存時都遵循一些協議,在讀寫時要根據協議來進行操作,這個協議就是MESI協議(緩存一致性協議)
在不同的硬件生產商和不同的操作系統下,內存的訪問邏輯有一定的差異,結果就是當你的代碼在某個系統環境下運行良好,并且線程安全,但是換了個系統就出現各種問題;
這是因為不同的處理器,在處理器優化和指令重排等方面存在差異,造成同樣的代碼,在經過不同的處理器優化和指令重排后,最后執行出來的結果可能不同,這是我們所不能接受的。
JMM就應運而生了,究其根本就是為了解決在并發環境下,保證數據的安全,滿足場景的可見性、原子性、有序性。
狀態 | 描述 | 監聽任務 |
---|---|---|
M 修改 (Modified) | 該Cache line有效,數據被修改了,和內存中的數據不一致,數據只存在于本Cache中。 | 緩存行必須時刻監聽所有試圖讀該緩存行相對就主存的操作,這種操作必須在緩存將該緩存行寫回主存并將狀態變成S(共享)狀態之前被延遲執行。 |
E 獨享(Exclusive) | 該Cache line有效,數據和內存中的數據一致,數據只存在于本Cache中。 | 緩存行也必須監聽其它緩存讀主存中該緩存行的操作,一旦有這種操作,該緩存行需要變成S(共享)狀態。 |
S 共享 (Shared) | 該Cache line有效,數據和內存中的數據一致,數據存在于很多Cache中。 | 緩存行也必須監聽其它緩存使該緩存行無效或者獨享該緩存行的請求,并將該緩存行變成無效(Invalid)。 |
I 無效 (Invalid) | 該Cache line無效。 | 無 |
原子性:把一個操作或者多個操作視為一個整體,在執行的過程不能被中斷的特性叫原子性。
為了最大化的利用CPU,CPU采用時間片的方式,切換線程執行。而在切換線程的過程中會導致原子性問題
進程和線程的本質是增加并行/并發的任務數量來提高CPU的執行效率,緩存的本質是通過減少IO時間來提升CPU的利用率。
CPU指令優化的初衷就是想通過調整CPU指令的執行順序或異步化的操作來提升CPU執行指令的效率。
為了使得處理器內部的運算單元能盡量被充分利用,處理器可能會對輸入代碼進行亂序執行優化,處理器會在計算之后將亂序執行的結果重組,優化原則是保證重排序后不影響單線程執行結果(As If Serial)。
重排序的大體邏輯就是優先把CPU比較耗時的指令放到最先執行,然后在這些指令執行的空余時間來執行其他指令。**Java虛擬機的即時編譯器中也有類似的指令重排序優化。
(編譯操作-(語義優化)指令重排->處理器級別重排(指令優化)->內存玉華指令重排(內存分析優化))
為了保證共享內存使用的正確性(原子性、有序性、可見性),內存模型定義了共享內存中多線程讀寫操作的規范。通過這些規則來規范對主內存的讀寫,從而保障指令的正確執行。
解決了CPU多級緩存、處理器優化、指令重排等導致的內存訪問問題,保證了并發場景下的一致性、原子性和有序性。
JMM是一種規范,目的是解決由于多線程通過共享內存進行通信時,存在的工作內存數據不一致、編譯器對代碼指令重排序、處理器對代碼亂序執行、CPU切換線程等帶來的問題。
JMM從java 5開始的JSR-133發布后,就比較成熟完善了;
JMM是符合內存模型規范的,屏蔽了各種硬件和操作系統的訪問差異的,保證了Java程序在各種平臺下對內存的訪問都能保證效果一致的機制。(如同JVM一樣標準規范)。
Java線程內存模型規定:
所有的變量都存儲在主內存中,每條線程還有自己的工作內存,線程的工作內存中保存了該線程中是用到的變量的主內存副本拷貝,線程對變量的所有操作都必須在工作內存中進行,而不能直接讀寫主內存。
不同的線程之間也無法直接訪問對方工作內存中的變量,線程間變量的傳遞均需要自己的工作內存和主存之間進行數據同步進行,也規定了如何做數據同步以及什么時候做數據同步。
內存交互操作有8種,虛擬機實現必須保證每一個操作都是原子的,不可在分的:
lock(鎖定):作用于主內存的變量,把一個變量標識為線程獨占狀態
unlock(解鎖):作用于主內存的變量,它把一個處于鎖定狀態的變量釋放出來,釋放后的變量才可以被其他線程鎖定
read(讀取):作用于主內存變量,它把一個變量的值從主內存傳輸到線程的工作內存中,以便隨后的load動作使用
load(載入):作用于工作內存的變量,它把read操作從主存中變量放入工作內存中;
use(使用):作用于工作內存中的變量,它把工作內存中的變量傳輸給執行引擎,每當虛擬機遇到一個需要使用到變量的值,就會使用到這個指令
assign(賦值):作用于工作內存中的變量,它把一個從執行引擎中接受到的值放入工作內存的變量副本中
store(存儲):作用于主內存中的變量,它把一個從工作內存中一個變量的值傳送到主內存中,以便后續的write使用
write(寫入):作用于主內存中的變量,它把store操作從工作內存中得到的變量的值放入主內存的變量中
不允許read和load、store和write操作之一單獨出現,即使用了read必須load,使用了store必須write
不允許線程丟棄他最近的assign操作,即工作變量的數據改變了之后,必須告知主存,不允許一個線程將沒有assign的數據從工作內存同步回主內存。
一個新的變量必須在主內存中誕生,不允許工作內存直接使用一個未被初始化的變量。就是對變量實施use、store操作之前,必須經過load、assign操作。
一個變量同一時間只有一個線程能對其進行lock。多次lock后,必須執行相同次數的unlock才能解鎖。
如果對一個變量進行lock操作,會清空所有工作內存中此變量的值,在執行引擎使用這個變量前,必須重新load或assign操作初始化變量的值。
如果一個變量沒有被lock,就不能對其進行unlock操作。也不能unlock一個被其他線程鎖住的變量。
對一個變量進行unlock操作之前,必須把此變量同步回主內存。
是指當多個線程訪問同一個變量時,一個線程修改了這個變量的值,其他線程能夠立即看得到修改的值;
Java可見性,可以使用 volatile 關鍵字,那就是被其修飾的變量在被修改后會立即同步到主內存(其實也是要反應時間的)主要靠內存屏障;
Java 中的 Synchronized 和 Final 兩個關鍵字也可以實現可見性;
是指在一個操作中被當作一個整體,要么一起執行,要么就不執行;
為了保證原子性,Java提供了兩個高級的字節碼指令 Monitorenter 和 Monitorexit,對應的關鍵字就是 Synchronized;
程序執行的順序按照代碼的先后順序執行;
可以使用 synchronized 和 volatile 來保證多線程之間操作的有序性,只是兩者實現的方式不一樣,volatile 關鍵字會禁止指令重排,synchronized 關鍵字保證同一時刻只允許一條線程操作;
到這里,我們似乎發現了,Synchronized 好像可以同時滿足三種特征,這也是Synchronized 被用的很頻繁的原因,但是 Synchronized 是比較影響性能的,雖然編譯器提供了很多鎖優化技術,但是也不建議過度使用。
分析一個并發程序是否安全,更多時候其實都依賴Happen-Before原則進行分析。
就是當A操作先行發生于B操作,則在發生B操作的時候,操作A產生的影響能被B觀察到,“影響”包括修改了內存中的共享變量的值、發送了消息、調用了方法等;
程序次序規則(Program Order Rule):在一個線程內,程序的執行規則跟程序的書寫規則是一致的,從上往下執行。
管程鎖定規則(Monitor Lock Rule):一個Unlock的操作肯定先于下一次Lock的操作。這里必須是同一個鎖。同理我們可以認為在synchronized同步同一個鎖的時候,鎖內先行執行的代碼,對后續同步該鎖的線程來說是完全可見的。
volatile變量規則(volatile Variable Rule):對同一個volatile的變量,先行發生的寫操作,肯定早于后續發生的讀操作
線程啟動規則(Thread Start Rule):Thread對象的start()方法先行發生于此線程的沒一個動作
線程中止規則(Thread Termination Rule):Thread對象的中止檢測(如:Thread.join(),Thread.isAlive()等)操作,必行晚于線程中所有操作
線程中斷規則(Thread Interruption Rule):對線程的interruption()調用,先于被調用的線程檢測中斷事件(Thread.interrupted())的發生
對象中止規則(Finalizer Rule):一個對象的初始化方法先于一個方法執行Finalizer()方法
傳遞性(Transitivity):如果操作A先于操作B、操作B先于操作C,則操作A先于操作C
到此,關于“Java內存模型的工作模式是什么”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。