JMM即爲JAVA 內存模型(java memory model)。由於在不一樣的硬件生產商和不一樣的操做系統下,內存的訪問邏輯有必定的差別,結果就是當你的代碼在某個系統環境下運行良好,而且線程安全,可是換了個系統就出現各類問題。Java內存模型,就是爲了屏蔽系統和硬件的差別,讓一套代碼在不一樣平臺下能到達相同的訪問結果。JMM從java 5開始的JSR-133發佈後,已經成熟和完善起來。html
JMM規定了內存主要劃分爲主內存和工做內存兩種。此處的主內存和工做內存跟JVM內存劃分(堆、棧、方法區)是在不一樣的層次上進行的,若是非要對應起來,主內存對應的是Java堆中的對象實例部分,工做內存對應的是棧中的部分區域,從更底層的來講,主內存對應的是硬件的物理內存,工做內存對應的是寄存器和高速緩存。java
JVM在設計時候考慮到,若是JAVA線程每次讀取和寫入變量都直接操做主內存,對性能影響比較大,因此每條線程擁有各自的工做內存,工做內存中的變量是主內存中的一份拷貝,線程對變量的讀取和寫入,直接在工做內存中操做,而不能直接去操做主內存中的變量。可是這樣就會出現一個問題,當一個線程修改了本身工做內存中變量,對其餘線程是不可見的,會致使線程不安全的問題。由於JMM制定了一套標準來保證開發者在編寫多線程程序的時候,可以控制何時內存會被同步給其餘線程。程序員
內存交互操做有8種,虛擬機實現必須保證每個操做都是原子的,不可在分的(對於double和long類型的變量來講,load、store、read和write操做在某些平臺上容許例外)緩存
JMM對這八種指令的使用,制定了以下規則:安全
JMM對這八種操做規則和對volatile的一些特殊規則就能肯定哪裏操做是線程安全,哪些操做是線程不安全的了。可是這些規則實在複雜,很難在實踐中直接分析。因此通常咱們也不會經過上述規則進行分析。更多的時候,使用java的happen-before規則來進行分析。多線程
原子性:例如上面八項操做,在操做系統裏面是不可分割的單元。被synchronized關鍵字或其餘鎖包裹起來的操做也能夠認爲是原子的。從一個線程觀察另一個線程的時候,看到的都是一個個原子性的操做。併發
1 synchronized (this) { 2 a=1; 3 b=2; 4 }
例如一個線程觀察另一個線程執行上面的代碼,只能看到a、b都被賦值成功結果,或者a、b都還沒有被賦值的結果。app
可見性:每一個工做線程都有本身的工做內存,因此當某個線程修改完某個變量以後,在其餘的線程中,未必能觀察到該變量已經被修改。volatile關鍵字要求被修改以後的變量要求當即更新到主內存,每次使用前從主內存處進行讀取。所以volatile能夠保證可見性。除了volatile之外,synchronized和final也能實現可見性。synchronized保證unlock以前必須先把變量刷新回主內存。final修飾的字段在構造器中一旦完成初始化,而且構造器沒有this逸出,那麼其餘線程就能看到final字段的值。
框架
有序性:java的有序性跟線程相關。若是在線程內部觀察,會發現當前線程的一切操做都是有序的。若是在線程的外部來觀察的話,會發現線程的全部操做都是無序的。由於JMM的工做內存和主內存之間存在延遲,並且java會對一些指令進行從新排序。volatile和synchronized能夠保證程序的有序性,不少程序員只理解這兩個關鍵字的執行互斥,而沒有很好的理解到volatile和synchronized也能保證指令不進行重排序。工具
被final修飾的變量,相比普通變量,內存語義有一些不一樣。具體以下:
1 public class FinalConstructor { 2 3 final int a; 4 5 int b; 6 7 static FinalConstructor finalConstructor; 8 9 public FinalConstructor() { 10 a = 1; 11 b = 2; 12 } 13 14 public static void write() { 15 finalConstructor = new FinalConstructor(); 16 } 17 18 public static void read() { 19 FinalConstructor constructor = finalConstructor; 20 int A = constructor.a; 21 int B = constructor.b; 22 } 23 }
假設如今有線程A執行FinalConstructor.write()方法,線程B執行FinalConstructor.read()方法。
對應上述的Final的第一條規則,由於JMM禁止把Final域的寫重排序到構造器的外部,而對普通變量沒有這種限制,因此變量A=1,而變量B可能會等於2(構造完成),也有可能等於0(第11行代碼被重排序到構造器的外部)。
對應上述的Final的第二條規則,若是constructor的引用不爲null,A必然爲1,要麼constructor爲null,拋出空指針異常。保證讀final域以前,必定會先讀該對象的引用。可是普通對象就沒有這種規則。
(上述的Final規則反覆測試,遺憾的是我並無能模擬出來普通變量不能正常構造的結果)
在常規的開發中,若是咱們經過上述規則來分析一個併發程序是否安全,估計腦袋會很疼。由於更多時候,咱們是分析一個併發程序是否安全,其實都依賴Happen-Before原則進行分析。Happen-Before被翻譯成先行發生原則,意思就是當A操做先行發生於B操做,則在發生B操做的時候,操做A產生的影響能被B觀察到,「影響」包括修改了內存中的共享變量的值、發送了消息、調用了方法等。
Happen-Before的規則有如下幾條
以上就是Happen-Before中的規則。經過這些條件的斷定,仍然很難判斷一個線程是否能安全執行,畢竟在咱們的時候線程安全多數依賴於工具類的安全性來保證。想提升本身對線程是否安全的判斷能力,必然須要理解所使用的框架或者工具的實現,並積累線程安全的經驗。