簡介
Java虛擬機(jī)規(guī)范中試圖定義了一種Java內(nèi)存模型(Java Memory Model,JMM)來屏蔽掉各種硬件和操作系統(tǒng)的內(nèi)存訪問差異,以實(shí)現(xiàn)讓Java程序在各種平臺(tái)下都能達(dá)到一致的內(nèi)存訪問效果。

主內(nèi)存與工作內(nèi)存
- 主內(nèi)存-Main Memory
Java內(nèi)存模型規(guī)定來所有變量都存儲(chǔ)在主內(nèi)存中 - 工作內(nèi)存-Working Memory
每條線程都有自己的工作內(nèi)存,線程的工作內(nèi)存中保存了被該線程使用到的變量的主內(nèi)存副本拷貝,線程對變量的所有操作(讀取、賦值等)都必須在工作內(nèi)存中進(jìn)行,而不能直接讀寫主內(nèi)存中的變量
不同線程之間也無法直接訪問其他工作內(nèi)存中的變量,線程間變量值的傳遞均需要通過主內(nèi)存來完成 - 對應(yīng)的JVM內(nèi)存區(qū)域
主內(nèi)存對應(yīng)于Java堆中的對象實(shí)例數(shù)據(jù)部分,而工作內(nèi)存則對應(yīng)于虛擬機(jī)棧中的部分區(qū)域
內(nèi)存間交互操作
JMM中定義了8種操作方式,虛擬機(jī)實(shí)現(xiàn)時(shí)必須保證每一種操作都是原子的、不可再分的
- lock-鎖定
把主內(nèi)存的變量標(biāo)識(shí)為一條線程獨(dú)占的狀態(tài) - unlock-解鎖
把一個(gè)處于鎖定狀態(tài)的主內(nèi)存的變量釋放,釋放后的變量才可以被其他線程鎖定 - read-讀取
把主內(nèi)存變量的值傳輸?shù)骄€程的工作內(nèi)存中,以便隨后的load操作使用 - load-載入
把read操作中從主內(nèi)存中得到的變量值放入工作內(nèi)存的變量副本中 - use-使用
把工作內(nèi)存中的一個(gè)變量的值傳遞給執(zhí)行引擎,每當(dāng)虛擬機(jī)遇到一個(gè)需要使用到變量的值的字節(jié)碼指令時(shí)就會(huì)執(zhí)行這個(gè)操作 - assign-賦值
把一個(gè)從執(zhí)行引擎接收到的值賦值給工作內(nèi)存的變量,每當(dāng)虛擬機(jī)遇到一個(gè)給變量賦值的字節(jié)碼指令時(shí)執(zhí)行這個(gè)操作 - store-存儲(chǔ)
把工作內(nèi)存中的變量的值傳送到主內(nèi)存中,以便隨后的wirte操作使用 - write-寫入
把store操作從工作內(nèi)存中得到的變量的值放到主內(nèi)存的變量中
原子性、可見性、有序性
JMM是圍繞著在并發(fā)過程中如何處理原子性、可見性和有序性這三個(gè)特征來建立的
原子性-Atomicity
JMM提供了lock和unlock操作來滿足這種需求,lock和unlock用戶不能直接使用但是提供了更高層次的字節(jié)碼指令monitorenter和monitorexit來隱式地使用這兩個(gè)操作,反映到Java代碼就是synchronized
可見性-Visibility
可見性是指當(dāng)一個(gè)線程修改了共享變量的值,其他線程能夠立即得知這個(gè)修改,共有三種方式實(shí)現(xiàn):
- volatile
volatile的特殊規(guī)則保證了新值能立即同步到主內(nèi)存,以及每次使用前立即從主內(nèi)存刷新 - synchronized
會(huì)對變量進(jìn)行l(wèi)ock操作,對于一個(gè)變量執(zhí)行unlock操作之前,必須先把變量同步回主內(nèi)存中(執(zhí)行store、write) - final
final 常量無需同步,就能被其它線程正確訪問
有序性-Ordering
如果在本線程內(nèi)觀察,所有的操作都是有序的;如果在一個(gè)線程中觀察另一個(gè)線程,所有的操作都是無序的
前半句指線程內(nèi)表現(xiàn)為串行的語意,后半句是指“指令重排序”和“工作內(nèi)存于主內(nèi)存同步延遲”
volatile和synchronized兩個(gè)關(guān)鍵字來保證線程之間操作的有序性
- volatile
volatile關(guān)鍵字本身就包含了禁止指令重排序的語義 - synchronized
synchronized是由“一個(gè)變量在同一時(shí)刻只允許一條線程對其進(jìn)行l(wèi)ock操作”這條規(guī)則獲得的,從而保證兩個(gè)同步塊只能串行地進(jìn)入
happens-before原則
happens-before(先行發(fā)生)原則,先行發(fā)生原則是JMM中定義的兩項(xiàng)操作的偏序關(guān)系,具體如下
- 程序順序規(guī)則-
Program Order Rule
如果程序中操作A在操作B之前,那么線程中A操作將在B操作之前執(zhí)行 - 監(jiān)視器鎖規(guī)則-
Monitor Lock Rule
unlock(退出同步方法/塊)發(fā)生在每次后續(xù)獲取同一個(gè)監(jiān)視器鎖lock之前
例如:操作A進(jìn)行l(wèi)ock,操作B想要進(jìn)行l(wèi)ock之前,操作A必須進(jìn)行unlock - volatile變量規(guī)則-
Volatile Variabel Rule
volatile變量的,在每次讀取操作之前,先執(zhí)行寫入操作 - 線程啟動(dòng)規(guī)則-
Thread Start Rule
Thread的start()方法為該線程的run()方法之前進(jìn)行的操作 - 線程結(jié)束規(guī)則-
Thread Termination Rule
線程的所有操作都發(fā)生在線程終止之前 - 線程中斷規(guī)則-
Thread Interruption Rule
當(dāng)一個(gè)線程在另一個(gè)線程上調(diào)用interrupt時(shí),必須在被中斷線程檢測到interrupt調(diào)用之前執(zhí)行 - 對象終結(jié)規(guī)則-
Finalizer Rule
對象的構(gòu)造函數(shù)必須在啟動(dòng)該對象的Finalizer操作之前完成 - 傳遞性-
Transitivity
如果操作A在操作B之前執(zhí)行,并且操作B在操作C之前執(zhí)行,那么操作A必須在操作C之前執(zhí)行
線程狀態(tài)轉(zhuǎn)換
Java中定義了5中線程狀態(tài),在任意一個(gè)時(shí)間點(diǎn),一個(gè)線程只能有且只有其中的一種狀態(tài)
- 新建-
New
創(chuàng)建后尚未啟動(dòng)的線程處于這種狀態(tài) - 運(yùn)行-
Runable
Runbale包含了操作系統(tǒng)線程狀態(tài)中的Running和Ready,也就是處于此狀態(tài)的線程可能正在執(zhí)行,也有可能正在等待CPU為它分配執(zhí)行時(shí)間 - 無限等待-Waiting
處于這種狀態(tài)的線程不會(huì)被分配CPU執(zhí)行時(shí)間,它們需要等待被其他線程顯示的喚醒,一下方式會(huì)讓線程陷入無期限的等待狀態(tài)- 無timeout參數(shù)的Object.wait()
- 無timeout參數(shù)的Thread.join()
- LockSupport.park()
- 限期等待-Timed Waiting
處于這種狀態(tài)的線程也不會(huì)被分配CPU執(zhí)行時(shí)間,不過無需等待被其他線程顯式地喚醒,在一定時(shí)間之后會(huì)由系統(tǒng)自動(dòng)喚醒,一下方法會(huì)讓線程進(jìn)入限期等待狀態(tài)- Thread.sleep()
- 設(shè)置timeout參數(shù)的Object.wait()
- 設(shè)置timeout參數(shù)的Thread.join()
- LockSupport.parkNanos()
- LockSupport.parkUntil()
- 阻塞-Blocked
等待獲取到一個(gè)排他鎖,在程序等待進(jìn)入同步區(qū)域的時(shí)候,線程進(jìn)入這種狀態(tài) - 結(jié)束-Terminated
已終止線程的線程狀態(tài),線程已經(jīng)結(jié)束執(zhí)行
轉(zhuǎn)換關(guān)系如圖所示:
