happens-before是JMM最核心的概念,理解happens-before是理解JMM的關鍵。程序員
一.JMM的設計算法
首先,讓咱們先分析一下JMM的設計意圖。從JMM的設計者的角度,在設計JMM的時候要考慮一下兩個關鍵因素:
1.程序員對內存模型的使用。程序員但願內存模型易於理解、易於編程。程序員但願基於一個強內存模型來編寫代碼。編程
2.編譯器和處理器對內存模型的實現。編譯器和處理器但願內存模型對它們的束縛越少越好,這樣它們就能夠作儘量多的優化來提升性能。編譯器和處理器但願實現一個弱內存模型。多線程
上述兩個因素相互矛盾,因此JSR-133專家組在設計JMM時的核心目標就是找到一個好的平衡點:一方面,要爲程序員提供足夠強的內存可見性保證;另外一方面,對編譯器和處理器的限制要儘量地放鬆。下面讓咱們來看JSR-133是如何實現這一目標的。app
double pi = 3.14; // A
double r = 1.0; // B
double area = pi * r * r; // C
上述計算圓的面積的代碼中,存在三個happens-before關係:
A happens-before B分佈式
B happens-before C性能
A happens-before C優化
在者三個happens-before關係中2和3是必須的,1是沒必要要的。所以JMM把happens-before要求禁止的重排序分了下面兩類spa
1.會改變程序執行結果的重排序線程
2.不會改變程序執行結果的重排序
JMM對這兩種不一樣性質的重排序,採用了不一樣的策略,以下:
1.對於會改變程序執行結果的重排序,JMM要求編譯器和處理器必須禁止這種重排序
2.對於不會改變程序執行結果的重排序,JMM對編譯器和處理器不作要求(JMM容許這種重排序)
下圖是JMM的設計示意圖
JMM向程序員提供的happens-before規則能知足程序員的要求,JMM的happens-before規則不但簡單易懂,並且也向程序員提供了足夠強的內存可見性保證。
JMM對編譯器和處理器的束縛已經儘量少。從上面的分析能夠看出,JMM實際上是在遵循一個基本原則:只要不改變程序的執行結果(指的是單線程程序和正確同步的多線程程序),編譯器和處理器怎麼優化都行。例如,若是編譯器通過細緻的分析後,認定一個鎖只會被單個線程訪問,那麼這個鎖能夠被消除。再如,若是編譯器通過細緻的分析後,認定一個volatile變量只會被單個線程訪問,那麼編譯器能夠把這個volatile變量看成一個普通變量來對待。這些優化既不會改變程序的執行結果,又能提升程序的執行效率。
二.happens-before的定義
happens-before的概念最初由Leslie Lamport在其一篇影響深遠的論文(《Time,Clocks andthe Ordering of Events in a Distributed System》)中提出。Leslie Lamport使用happens-before來定義分佈式系統中事件之間的偏序關係(partial ordering)。Leslie Lamport在這篇論文中給出了一個分佈式算法,該算法能夠將該偏序關係擴展爲某種全序關係。
JSR-133使用happens-before的概念來指定兩個操做之間的執行順序。因爲這兩個操做能夠在一個線程以內,也能夠是在不一樣線程之間。所以,JMM能夠經過happens-before關係向程序員提供跨線程的內存可見性保證(若是A線程的寫操做a與B線程的讀操做b之間存在happensbefore關係,儘管a操做和b操做在不一樣的線程中執行,但JMM向程序員保證a操做將對b操做可見)。
《JSR-133:Java Memory Model and Thread Specification》對happens-before關係的定義以下:
1.若是一個操做happens-before另外一個操做,那麼第一個操做的執行結果將對第二個操做可見,並且第一個操做的執行順序排在第二個操做以前。
2.兩個操做之間存在happens-before關係,並不意味着Java平臺的具體實現必需要按照happens-before關係指定的順序來執行。若是重排序以後的執行結果,與按happens-before關係來執行的結果一致,那麼這種重排序並不非法。
上面1是JMM對程序員的承諾。從程序員的角度來講,能夠這樣理解happens-before關係:若是A happens-before B,那麼Java內存模型將向程序員保證——A操做的結果將對B可見,且A的執行順序排在B以前。注意,這只是Java內存模型向程序員作出的保證!
上面2是JMM對編譯器和處理器衝排序的約束。MM實際上是在遵循一個基本原則:只要不改變程序的執行結果,編譯器和處理器怎麼優化都行。happens-before這麼作的目的,都是爲了在不改變程序執行結果的前提下,儘量地提升程序執行的並行度。
三.happens-before規則
1.程序順序規則:一個線程中的每一個操做,happens-before於該線程中的任意後續操做
2.監視器鎖規則:對一個鎖的解鎖,happens-before於隨後對這個鎖的加鎖
3.volatile變量規則:對一個volatile域的寫,happens-before於任意後續對這個volatile域的讀
4.傳遞性:若是A happens-before B,且B happens-before C,那麼A happens-before C
5.start規則:若是線程A執行操做ThreadB.start()(啓動線程B),那麼A線程的ThreadB.start()操做happens-before於線程B中的任意操做
6.join規則:若是線程A執行操做ThreadB.join()併成功返回,那麼線程B中的任意操做happens-before於線程A從ThreadB.join()操做成功返回。
四.鎖的內存語義
釋放鎖時,JMM會把該線程對應的本地內存中的共享變量刷新到主內存中;獲取鎖時會把該線程對應的本地內存置爲無效。