天天看點

JMM模型 java記憶體模型

JMM即為JAVA 記憶體模型(java memory model)。

JMM是一個抽象模型,它是建立在不同的作業系統和硬體層面之上,對問題進行了統一的抽象。

因為在不同的硬體生産商和不同的作業系統下,記憶體的通路邏輯有一定的差異,結果就是當你的代碼在某個系統環境下運作良好,并且線程安全,但是換了個系統就出現各種問題。Java記憶體模型,就是為了屏蔽系統和硬體的差異,讓一套代碼在不同平台下能到達相同的通路結果。

MM規定了記憶體主要劃分為主記憶體和工作記憶體兩種。此處的主記憶體和工作記憶體跟JVM記憶體劃分(堆、棧、方法區)是在不同的層次上進行的,如果非要對應起來,主記憶體對應的是Java堆中的對象執行個體部分,工作記憶體對應的是棧中的部分區域,從更底層的來說,主記憶體對應的是硬體的實體記憶體,工作記憶體對應的是寄存器和高速緩存。

JMM模型 java記憶體模型

JMM是如何控制CPU工作記憶體和主記憶體間的互動呢?看下面一張圖

JMM模型 java記憶體模型

記憶體互動操作有8種,虛拟機實作必須保證每一個操作都是原子的,不可在分的(對于double和long類型的變量來說,load、store、read和write操作在某些平台上允許例外)。

read(讀取): 讀取主記憶體的資料

load(載入): 将讀取的資料寫入工作記憶體

use(使用): 使用工作記憶體的變量副本

assign(指派): 将新的值賦給工作記憶體中的變量

store(存儲): 将工作記憶體變量寫入主記憶體

write(寫入): 将store過去的新的值賦給主記憶體變量

lock(加鎖): 将主記憶體變量加鎖,設定為獨占狀态

unlock(解鎖): 将主記憶體變量解鎖,其他線程可以對變量加鎖

原子性:例如上面八項操作,在作業系統裡面是不可分割的單元。被synchronized關鍵字或其他鎖包裹起來的操作也可以認為是原子的。從一個線程觀察另外一個線程的時候,看到的都是一個個原子性的操作。

可見性:每個工作線程都有自己的工作記憶體,是以當某個線程修改完某個變量之後,在其他的線程中,未必能觀察到該變量已經被修改。volatile關鍵字要求被修改之後的變量要求立即更新到主記憶體,每次使用前從主記憶體處進行讀取。是以volatile可以保證可見性。除了volatile以外,synchronized和final也能實作可見性。synchronized保證unlock之前必須先把變量重新整理回主記憶體。final修飾的字段在構造器中一旦完成初始化,并且構造器沒有this逸出,那麼其他線程就能看到final字段的值。

有序性:java的有序性跟線程相關。如果線上程内部觀察,會發現目前線程的一切操作都是有序的。如果線上程的外部來觀察的話,會發現線程的所有操作都是無序的。因為JMM的工作記憶體和主記憶體之間存在延遲,而且java會對一些指令進行重新排序。volatile和synchronized可以保證程式的有序性,很多程式員隻了解這兩個關鍵字的執行互斥,而沒有很好的了解到volatile和synchronized也能保證指令不進行重排序。

  在正常的開發中,如果我們通過上述規則來分析一個并發程式是否安全,估計腦殼會很疼。因為更多時候,我們是分析一個并發程式是否安全,其實都依賴Happen-Before原則進行分析。Happen-Before被翻譯成先行發生原則,意思就是當A操作先行發生于B操作,則在發生B操作的時候,操作A産生的影響能被B觀察到,“影響”包括修改了記憶體中的共享變量的值、發送了消息、調用了方法等。

  Happen-Before的規則有以下幾條

程式次序規則(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

  以上就是Happen-Before中的規則。通過這些條件的判定,仍然很難判斷一個線程是否能安全執行,畢竟在我們的時候線程安全多數依賴于工具類的安全性來保證。想提高自己對線程是否安全的判斷能力,必然需要了解所使用的架構或者工具的實作,并積累線程安全的經驗。

并發安全性問題有兩個因素,一個是高速緩存導緻的可見性問題,另一個是指令重排序。

對于緩存一緻性問題,有總線鎖和緩存鎖,緩存鎖是基于MESI協定。

對于指令重排序,硬體層面提供了記憶體屏障指令。 而JMM在這個基礎上提供了volatile、final等關鍵字,使得開發者可以在合适的時候增加相應相應的關鍵字來禁止高速緩存和禁止指令重排序來解決可見性和有序性問題。

莫聽穿林打葉聲,何妨吟嘯且徐行!!!