什么是JMM
內存模型描述了程序中各個變量(實例域、靜態域和數組元素)之間的關系,以及在實際計算機系統中將變量存儲到內存和從內存中取出變量這樣的底層細節
JMM(Java Memory Model)即Java內存模型
JMM
JMM規定了所有的變量都存儲在主內存(Main Memory)中。每個線程還有自己的工作內存(Working Memory),線程的工作內存中保存了該線程使用到的變量的主內存的副本拷貝,線程對變量的所有操作(讀取、賦值等)都必須在工作內存中進行,而不能直接讀寫主內存中的變量(volatile變量仍然有工作內存的拷貝,但是由於它特殊的操作順序性規定,所以看起來如同直接在主內存中讀寫訪問一般)。不同的線程之間也無法直接訪問對方工作內存中的變量,線程之間值的傳遞都需要通過主內存來完成。

JMM三個特征
Java內存模型是圍繞着並發編程中原子性、可見性、有序性這三個特征來建立的。
原子性
一個操作不能被打斷,要么全部執行完畢,要么不執行。在這點上有點類似於事務操作,要么全部執行成功,要么回退到執行該操作之前的狀態。
基本類型數據的訪問大都是原子操作,long 和double類型的變量是64位,但是在32位JVM中,32位的JVM會將64位數據的讀寫操作分為2次32位的讀寫操作來進行,這就導致了long、double類型的變量在32位虛擬機中是非原子操作,數據有可能會被破壞,也就意味着多個線程在並發訪問的時候是線程非安全的。
可見行
一個線程對共享變量做了修改之后 ,其他的線程立即能夠看到(感知到)該變量的這種修改(變化)。
無論是普通變量還是volatile變量都是如此,區別在於:volatile的特殊規則保證了volatile變量值修改后的新值立刻同步到主內存,每次使用volatile變量前立即從主內存中刷新,因此volatile保證了多線程之間的操作變量的可見性,而普通變量則不能保證這一點。
除了volatile關鍵字能實現可見性之外,還有synchronized,Lock,final也是可以的。
使用synchronized關鍵字,在同步方法/同步塊開始時(Monitor Enter),使用共享變量時會從主內存中刷新變量值到工作內存中(即從主內存中讀取最新值到線程私有的工作內存中),在同步方法/同步塊結束時(Monitor Exit),會將工作內存中的變量值同步到主內存中去(即將線程私有的工作內存中的值寫入到主內存進行同步)。
使用Lock接口的最常用的實現ReentrantLock(重入鎖)來實現可見性:當我們在方法的開始位置執行lock.lock()方法,這和synchronized開始位置(Monitor Enter)有相同的語義,即使用共享變量時會從主內存中刷新變量值到工作內存中(即從主內存中讀取最新值到線程私有的工作內存中),在方法的最后finally塊里執行lock.unlock()方法,和synchronized結束位置(Monitor Exit)有相同的語義,即會將工作內存中的變量值同步到主內存中去(即將線程私有的工作內存中的值寫入到主內存進行同步)。
final關鍵字的可見性是指:被final修飾的變量,在構造函數數一旦初始化完成,並且在構造函數中並沒有把“this”的引用傳遞出去(“this”引用逃逸是很危險的,其他的線程很可能通過該引用訪問到只“初始化一半”的對象),那么其他線程就可以看到final變量的值。
有序性
對於一個線程的代碼而言,我們總是以為代碼的執行是從前往后的,依次執行的。這么說不能說完全不對,在單線程程序里,確實會這樣執行;但是在多線程並發時,程序的執行就有可能出現亂序。用一句話可以總結為:在本線程內觀察,操作都是有序的;如果在一個線程中觀察另外一個線程,所有的操作都是無序的。前半句是指“線程內表現為串行語義(WithIn Thread As-if-Serial Semantics)”,后半句是指“指令重排”現象和“工作內存和主內存同步延遲”現象。
一個最經典的例子就是銀行匯款問題,一個銀行賬戶存款100,這時一個人從該賬戶取10元,同時另一個人向該賬戶匯10元,那么余額應該還是100。那么此時可能發生這種情況,A線程負責取款,B線程負責匯款,A從主內存讀到100,B從主內存讀到100,A執行減10操作,並將數據刷新到主內存,這時主內存數據100-10=90,而B內存執行加10操作,並將數據刷新到主內存,最后主內存數據100+10=110,顯然這是一個嚴重的問題,我們要保證A線程和B線程有序執行,先取款后匯款或者先匯款后取款,此為有序性。
Java提供了兩個關鍵字volatile和synchronized來保證多線程之間操作的有序性,volatile關鍵字本身通過加入內存屏障來禁止指令的重排序,而synchronized關鍵字通過一個變量在同一時間只允許有一個線程對其進行加鎖的規則來實現,
在單線程程序中,不會發生“指令重排”和“工作內存和主內存同步延遲”現象,只在多線程程序中出現。
