JMM之happens-before

在JMM中有一個很重要的概念對於咱們瞭解JMM有很大的幫助,那就是happens-before規則。happens-before規則很是重要,它是判斷數據是否存在競爭、線程是否安全的主要依據。JSR-133S使用happens-before概念闡述了兩個操做之間的內存可見性。在JMM中,若是一個操做的結果須要對另外一個操做可見,那麼這兩個操做則存在happens-before關係。java

那什麼是happens-before呢?在JSR-133中,happens-before關係定義以下:安全

  1. 若是一個操做happens-before另外一個操做,那麼意味着第一個操做的結果對第二個操做可見,並且第一個操做的執行順序將排在第二個操做的前面。
  2. 兩個操做之間存在happens-before關係,並不意味着Java平臺的具體實現必須按照happens-before關係指定的順序來執行。若是重排序以後的結果,與按照happens-before關係來執行的結果一致,那麼這種重排序並不非法(也就是說,JMM容許這種重排序)

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. 線程啓動規則:Thread對象的start()方法,happens-before於這個線程的任意後續操做。
  6. 線程終止規則:線程中的任意操做,happens-before於該線程的終止監測。咱們能夠經過Thread.join()方法結束、Thread.isAlive()的返回值等手段檢測到線程已經終止執行。
  7. 線程中斷操做:對線程interrupt()方法的調用,happens-before於被中斷線程的代碼檢測到中斷事件的發生,能夠經過Thread.interrupted()方法檢測到線程是否有中斷髮生。
  8. 對象終結規則:一個對象的初始化完成,happens-before於這個對象的finalize()方法的開始。

以上8條happens-before規則都比較簡單,這裏LZ只分析第3條volatile變量規則,分析以下:app

happens-before之volatile規則

從上圖中,咱們看到存在4條happens-before關係,它們分別以下:this

  • 1 happens-before 2 和 3 happens-before 4 是有由程序順序性規則產生的。
  • 2 happens-before 3 是由volatile規則產生的。上面提到過,一個volatile變量的讀,總能看到以前對這個volatile變量的寫入。
  • 1 happens-before 4 是由傳遞性規則產生的。

讀到這裏,可能不少童鞋會把happens-before理解爲「時間上的前後順序」,在這裏LZ特別強調happens-hefore不能理解爲「時間上的前後順序」,下面LZ用一段代碼解釋寫happens-before和「時間上的前後順序」的不一樣,代碼以下:線程

public class VolatileTest4 {
    private int a = 0;
    public int getA() {
        return a;
    }
    public void setA(int a) {
        this.a = a;
    }
}

上面代碼就是一組簡單的setter/getter方法,如今假設如今有兩個線程A和B,線程A先(這裏指時間上的先執行)執行setA(10),而後線程B訪問同一個對象的getA()方法,那麼此時線程B收到的返回值是對少呢?code

答案是:不肯定對象

咱們來一次分析下happens-before的各項原則:blog

  1. 這裏兩個方法分別是在兩個線程中被調用,不在一個線程中,這裏程序順序性就不適用了
  2. 代碼中沒有同步快,全部監視器規則也不適用
  3. 代碼中變量a是一個普通變量,因此volatile規則也不適用
  4. 後面的線程啓動、中斷、終止和對象的終結和這裏徹底沒有關係,所以這些規則也是不適用的
  5. 沒有一條happens-before適用,所以傳遞性規則也不適用

在這裏,雖然線程A在時間上先於線程B執行,可是因爲代碼徹底不適用happens-before規則,所以咱們沒法肯定先B收到的值時多少。也就是說上面代碼是線程不安全的。排序

對於上面代碼,那咱們如何修復線程不安全這個問題呢?這裏,咱們只要知足happens-before規則中二、3的任意一種規則就能夠了。即要麼把setter/getter方法定義爲synchronized方法,要麼在變量a上加volatile修飾符。

經過上面的例子,咱們能夠得出結論:一個操做「時間上的先發生」不表明這個操做會happens-before其它操做。那一個操做happens-before其它操做,是否就表示這個操做是「時間上先發生」的呢?答案也是否認的,咱們來看看下面一個示例:

int i = 1;
int m = 2;

上面兩個賦值操做在同一個線程中,根據程序順序性規則,「int i = 1;"這個操做happens-before 」int m = 2;「這個操做,可是」int m = 2;「這個操做徹底有可能被處理器先執行,這並不影響happens-before原則的正確性。由於這種重排序在JMM中是容許的。

最後咱們得出的結論是:時間前後順序與happens-before原則之間基本沒有太大的關係,因此咱們在衡量併發安全問題的時候不要受到時間順序的干擾,一切必須以happens-before原則爲準。

相關文章
相關標籤/搜索