1 Java內(nèi)存模型(JMM)
- Java線程間的通信由Java內(nèi)存模型(JMM)控制,JMM決定一個線程對共享變量的寫入何時對另一個線程可見
- JMM是一個抽象的概念,并非真實存在,它涵蓋了緩存、寫緩沖區(qū)、寄存器以及其他的硬件和編譯器的優(yōu)化
- JMM定義了線程和主內(nèi)存之間的抽象關系:
- 線程之間的共享變量存儲在主內(nèi)存中(從硬件角度來說就是內(nèi)存條)
- 每個線程都有一個私有的本地內(nèi)存,本地內(nèi)存中存儲了該線程用來讀/寫共享變量的副本(從硬件角度來說就是CPU的緩存,比如寄存器、L1、L2、L3緩存等)
- 同時JVM通過JMM來屏蔽各個硬件平臺和操作系統(tǒng)的內(nèi)存訪問差異,以實現(xiàn)讓Java程序在各種平臺下都能達到一致的內(nèi)存訪問效果
- 重要聲明: JMM所描述的主內(nèi)存、工作內(nèi)存與Java內(nèi)存區(qū)域的堆棧不是一回事,更準確是主內(nèi)存就是內(nèi)存條,為了提高性能,JVM可能會讓工作內(nèi)存優(yōu)先存儲在寄存器和高速緩存中,程序運行時主要訪問讀寫的也是工作內(nèi)存
2 JMM的核心原則
- JMM的關鍵技術點都是圍繞多線程的原子性、可見性和有序性展開的
- 多線程并發(fā)的法寶:外互斥、內(nèi)可見
2.1 原子性
- 原子性是指一個操作是不可中斷的,即多線程環(huán)境下,操作不能被其他線程干擾
2.2 可見性
- 可見性是指當一個線程修改了某一個共享變量的值,其他線程是否能夠立即知道該變更
- Java中普通的共享變量不保證可見性,因為其的修改被寫入內(nèi)存的時機是不確定的,多線程并發(fā)下很可能出現(xiàn)"臟讀"
- 緩存優(yōu)化或者硬件優(yōu)化或指令重排以及編輯器的優(yōu)化都可能導致一個線程修改不會立即被其他線程察覺
- Java提供volatile保證可見性:寫操作立即刷新到主內(nèi)存,讀操作直接從主內(nèi)存讀取
- Java同時還可以通過加鎖的同步性間接保證可見性:synchronized和Lock能保證同一時刻只有一個線程獲取鎖并執(zhí)行同步代碼,并在釋放鎖之后將變量的修改刷新到主內(nèi)存中
2.3 有序性
- 對于一個線程的執(zhí)行代碼而言,我們總是習慣性認為代碼的執(zhí)行總是從上到下,有序執(zhí)行
- 但為了提供性能,編譯器和處理器通常會對指令序列進行重新排序
- 指令重排可以保證串行語義一致,但沒有義務保證多線程間的語義也一致,即可能產(chǎn)生"臟讀"
3 JMM的抽象結構
JMM.jpg
4 JMM的影響范圍
- 在Java中,所有實例域、靜態(tài)域和數(shù)組元素都存儲在堆內(nèi)存中,堆內(nèi)存在線程間共享
- 局部變量、方法定義參數(shù)和異常處理器參數(shù)不在線程間共享,即不會有可見性問題也不受JMM影響
5 JMM的內(nèi)存可見性保證
- 單線程程序: 不會出現(xiàn)內(nèi)存可見性問題,不管重排序與否結果也會最終一致
- 正確同步的多線程程序: 將順序一致性,JMM通過限制重排序來為程序提供內(nèi)存可見性保證
- 未同步/未正確同步的多線程程序: JMM提供最小安全性保障-線程執(zhí)行時讀取到的值,要么是之前某個線程寫入的值,要么是默認值
1.png
6 JMM的隱患
6.1 寫緩沖區(qū)(工作內(nèi)存)
- 處理器使用寫緩沖區(qū)臨時保存向內(nèi)存寫入的數(shù)據(jù)
- 寫緩沖區(qū)可以保證指令流水線持續(xù)運行,避免由于處理器停頓等待向內(nèi)存寫入數(shù)據(jù)產(chǎn)生的延遲
- 寫緩沖區(qū)還可以通過批處理的方式刷新寫緩沖區(qū),以及合并寫緩沖區(qū)中對同一內(nèi)存地址的多次寫,減少對內(nèi)存總線的占用
6.2 寫緩沖區(qū)的隱患
- 雖然寫緩沖區(qū)好處多多,但問題在于寫緩沖區(qū)僅對其所在的處理器所見,即是處理器私有工作內(nèi)存
- 為了獲得較好的執(zhí)行性能,Java內(nèi)存模型并沒有限制執(zhí)行引擎使用處理器的寄存器或者高速緩存來提升指令執(zhí)行速度,也沒有限制編譯器對指令進行重排序
- 因此,在JMM會存在緩存一致性問題和指令重排序的問題