理解的第一個維度:核心知識點緩存
JMM本質上能夠理解爲,Java 內存模型規範了 JVM 如何提供按需禁用緩存和編譯優化的方法。具體來講,這些方法包括:app
volatile、synchronized 和 final 三個關鍵字ide
Happens-Before 規則函數
理解的第二個維度:可見性,有序性,原子性優化
原子性線程
在Java中,對基本數據類型的變量的讀取和賦值操做是原子性操做,即這些操做是不可被中斷的,要麼執行,要麼不執行。請分析如下哪些操做是原子性操做:code
x = 10; //語句1: 直接將數值10賦值給x,也就是說線程執行這個語句的會直接將數值10寫入到工做內存中
y = x; //語句2: 包含2個操做,它先要去讀取x的值,再將x的值寫入工做內存,雖然讀取x的值以及 將x的值寫入工做內存 這2個操做都是原子性操做,可是合起來就不是原子性操做了。
x++; //語句3:x++包括3個操做:讀取x的值,進行加1操做,寫入新的值。
x = x + 1; //語句4: 同語句3
上面4個語句只有語句1的操做具有原子性。對象
也就是說,只有簡單的讀取、賦值(並且必須是將數字賦值給某個變量,變量之間的相互賦值不是原子操做)纔是原子操做。blog
從上面能夠看出,Java內存模型只保證了基本讀取和賦值是原子性操做,若是要實現更大範圍操做的原子性,能夠經過synchronized和Lock來實現。因爲synchronized和Lock可以保證任一時刻只有一個線程執行該代碼塊,那麼天然就不存在原子性問題了,從而保證了原子性。事件
可見性
Java提供了volatile關鍵字來保證可見性。
當一個共享變量被volatile修飾時,它會保證修改的值會當即被更新到主存,當有其餘線程須要讀取時,它會去內存中讀取新值。
而普通的共享變量不能保證可見性,由於普通共享變量被修改以後,何時被寫入主存是不肯定的,當其餘線程去讀取時,此時內存中可能仍是原來的舊值,所以沒法保證可見性。
另外,經過synchronized和Lock也可以保證可見性,synchronized和Lock能保證同一時刻只有一個線程獲取鎖而後執行同步代碼,而且在釋放鎖以前會將對變量的修改刷新到主存當中。所以能夠保證可見性。
有序性
在Java裏面,能夠經過volatile關鍵字來保證必定的「有序性」(具體原理在下一節講述)。另外能夠經過synchronized和Lock來保證有序性,很顯然,synchronized和Lock保證每一個時刻是有一個線程執行同步代碼,至關因而讓線程順序執行同步代碼,天然就保證了有序性。固然JMM是經過Happens-Before 規則來保證有序性的。
關鍵字: volatile、synchronized 和 final Happens-Before 規則
上面提到了能夠用 volatile 和 synchronized 來保證有序性。除此以外,JVM 還規定了先行發生原則,讓一個操做無需控制就能先於另外一個操做完成。
Single Thread rule
在一個線程內,在程序前面的操做先行發生於後面的操做。
Monitor Lock Rule
一個 unlock 操做先行發生於後面對同一個鎖的 lock 操做。
Volatile Variable Rule
對一個 volatile 變量的寫操做先行發生於後面對這個變量的讀操做。
Thread Start Rule
Thread 對象的 start() 方法調用先行發生於此線程的每個動做。
Thread Join Rule
Thread 對象的結束先行發生於 join() 方法返回。
Thread Interruption Rule
對線程 interrupt() 方法的調用先行發生於被中斷線程的代碼檢測到中斷事件的發生,能夠經過 interrupted() 方法檢測到是否有中斷髮生。
Finalizer Rule
一個對象的初始化完成(構造函數執行結束)先行發生於它的 finalize() 方法的開始。
Transitivity
若是操做 A 先行發生於操做 B,操做 B 先行發生於操做 C,那麼操做 A 先行發生於操做 C。