從 JDK 5開始,Java 使用新的 JSR-133 內存模型,使用 happens-before 的概念來闡述操做間的可見性。java
JSR-133 對Happens-Before 的定義:程序員
Happens-Before Relationship Two actions can be ordered by a happens-before relationship. If one action > happens-before another, then the first is visible to and ordered before the second. It should be stressed that a happens-before relationship between two actions does not imply that those actions must occur in that order in a Java platform implementation. The happens-before relation mostly stresses orderings between two actions that conflict with each other, and defines when data races take place. There are a number of ways to induce a happens-before ordering, including:編程
- Each action in a thread happens-before every subsequent action in that thread.
- An unlock on a monitor happens-before every subsequent lock on that monitor.
- A write to a volatile field happens-before every subsequent read of that volatile.
- A call to start() on a thread happens-before any actions in the started thread.
- All actions in a thread happen-before any other thread successfully returns from a join() on that thread.
- If an action a happens-before an action b, and b happens before an action c, then a happensbefore c.
定義: 若是一個操做happens-before另外一個操做,那麼意味着第一個操做的結果對第二個操做可見,並且第一個操做的執行順序將排在第二個操做的前面。 兩個操做之間存在happens-before關係,並不意味着Java平臺的具體實現必須按照happens-before關係指定的順序來執行。若是重排序以後的結果,與按照happens-before關係來執行的結果一致,那麼這種重排序並不非法(也就是說,JMM容許這種重排序)。具體規則以下:安全
注:說明一下,網上搜出來有的是8條規則,我不知道還有兩條哪兒來的,JSR-133 裏面只有這六條。網上的還有下面兩條:bash
程序順序規則:一段代碼在單線程中執行的結果是有序的。注意是執行結果,由於虛擬機、處理器會對指令進行重排序。雖然重排序了,可是並不會影響程序的執行結果,因此程序最終執行的結果與順序執行的結果是一致的。故而這個規則只對單線程有效,在多線程環境下沒法保證正確性。多線程
監視器鎖規則:這個規則比較好理解,不管是在單線程環境仍是多線程環境,一個鎖處於被鎖定狀態,那麼必須先執行unlock操做後面才能進行lock操做。併發
volatile變量規則:這是一條比較重要的規則,它標誌着volatile保證了線程可見性。通俗點講就是若是一個線程先去寫一個volatile變量,而後一個線程去讀這個變量,那麼這個寫操做必定是happens-before讀操做的。app
線程啓動規則:假定線程A在執行過程當中,經過執行ThreadB.start()來啓動線程B,那麼線程A對共享變量的修改在接下來線程B開始執行後確保對線程B可見。post
線程終結規則:假定線程A在執行的過程當中,經過制定ThreadB.join()等待線程B終止,那麼線程B在終止以前對共享變量的修改在線程A等待返回後可見。學習
傳遞性規則:提現了happens-before原則具備傳遞性。
特別強調happens-hefore不能理解爲「時間上的前後順序」。 咱們來看以下代碼:
public class VolatileTest {
private int a = 0;
private int getA() {
return a;
}
private void setA(int a) {
this.a = a;
}
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 100; i++) {
VolatileTest volatileTest = new VolatileTest();
Thread thread1 = new Thread(() -> {
volatileTest.setA(10);
});
thread1.start();
Thread thread2 = new Thread(() -> {
System.out.print(volatileTest.getA()+" ");
});
thread2.start();
}
}
}
複製代碼
上面代碼就是一組簡單的setter/getter方法,如今假設如今有兩個線程 thread1 和 thread2,線程 thread1 先(這裏指時間上的先執行)執行setA(10),而後線程 thread2 訪問同一個對象的getA()方法,那麼此時線程B收到的返回值是對少呢?
答案:不肯定
0 0 0 0 10 0 10 10 10 0 10 0 10 10 10 10 10 0 10 10 0 0 0 10 0 10 10 10 0 10 0 10 10 10 0 10 10 0 10 10 10 0 0 10 10 0 10 0 10 10 10 10 10 10 10 10 10 10 0 0 0 10 10 0 10 0 10 0 0 0 10 10 0 10 10 10 10 10 10 10 10 10 10 10 0 10 10 10 0 10 10 10 10 10 0 10 0 10 0 0
複製代碼
雖然線程 thread1 在時間上先於線程 thread2 執行,可是因爲代碼徹底不適用happens-before規則,所以咱們沒法肯定先 thread2 收到的值時多少。也就是說上面代碼是線程不安全的。
從圖能夠看出:
一個happens-before規則對應於一個或多個編譯器和處理器重排序規則。對於Java程序員來講,happens-before規則簡單易懂,它避免Java程序員爲了理解JMM提供的內存可見性保證而去學習複雜的重排序規則以及這些規則的具體實現方法.
時間前後順序與happens-before原則之間基本沒有太大的關係,因此咱們在衡量併發安全問題的時候不要受到時間順序的干擾,一切必須以happens-before原則爲準。
簡單的說,happens-before 規則就是爲了讓程序猿更好的理解 JMM 提供的內存可見性而編寫的規則,讓程序猿能避免去學習編譯器和底層編譯原理的重排序規則。