Java內存模型-final域的內存語義

一 引言

  說到final你確定知道它是Java中的關鍵字,那麼它所在Java中的做用你知道嗎?不知道的話,請前往這篇瞭解下http://www.javashuo.com/article/p-vewbmfte-eq.htmlhtml

  今天咱們來講說final域在JMM中的內存語義。函數

二 final域的重排序規則

  開門見山,對於final域,編譯器和處理器必定要遵照兩個重排序規則(JSR-133才加強了final域):this

  1)在構造函數內對一個final域的寫入,與隨後把這個被構造對象的引用賦值給一個引用變量,這個兩個操做不能被重排序。
spa

  2)初次讀一個包含final域的對象的引用,與隨後初次讀這個final域,這兩個操做之間不能重排序。線程

  下面咱們經過案例來講明這兩點(假設線程1執行writer(),隨後另外一個線程執行reader()方法):code

public class FinalExample {
    static volatile boolean flag = true;
    int i = 0;
    final int j;
    static FinalExample obj;

    public FinalExample() { // 構造函數
        i = 1;              // 寫普通域
        j = 2;              // 寫final域
    }

    public static void writer() { // 線程1寫入
        obj = new FinalExample();
    }

    public static void reader() { // 線程2讀取
        FinalExample example = obj; // 讀對象引用
        System.out.println(example.i); // 讀普通域
        System.out.println(example.j); // 讀final域
    }
}

  寫final域的重排序規則禁止把final域的寫重排序到構造函數以外。這個規則的實現包含下面兩個方面:htm

  1)JMM禁止編譯器吧final域的寫重排序到構造函數以外。對象

  2)編譯器會在final域的寫以後,構造函數return以前,插入一個StoreStore屏障。這個屏障禁止處理器把final域的寫重排序到構造函數以外。blog

  因此線程1執行順序以下圖(其中寫普通域的順序沒法保證,理論上是存在下面三種狀況的,要想驗證普通域是否有重排序的結果有點難,由於沒法保證線程1把普通域重排序後,線程2可以讀取它以前的0值):排序

  讀final域的重排序規則是:在一個線程中,初次讀這個對象引用與初次讀該對象包含的final域,JMM禁止處理器重排序這兩個操做。其中編譯器會在讀final域操做的前面插入一個LoadLoad屏障。因爲插入了loadLoad屏障,讀普通域i的操做是不會重排序到讀final域,可是不保證它會重排到讀對象引用這個操做的前面。因此線程2的一個執行順序就能想象到了,這裏就不畫線程2的執行順序圖了。 

三 final域爲引用類型

  若是finaly域爲引用類型,JMM中是怎麼處理的呢?對於引用類型,寫final域的重排序規則對編譯器和處理器增長了以下約束:在構造函數內對一個final引用的對象的成員域的寫入,與隨後在構造函數外把這個被構造對象的引用賦值給一個引用變量,這兩個操做之間不能重排序。 

       以上及其以上都要注意:只針對於構造函數方法內。另外要想以上規則確保,還須要一個條件:在構造函數內部,不能讓這個被構造對象的引用被其餘線程可見,也就是對應引用不能再構造函數中「逸出」。以下案例:

  

class FinalReferenceEscapeExample {
    final int i;
    static FinalReferenceEscapeExample obj;

    public FinalReferenceEscapeExample() {
        i = 1;      // 1
        obj = this; // 2 this引用逸出
    }

    public static void writer() { // 線程1
        new FinalReferenceEscapeExample();
    }

    public static void reader() { // 線程2
        if (obj != null) {
            System.out.println(obj.i);
        }
    }
}

 

  上面程序,第一步寫final域與第二步是不保證重排序的。因此當第一步與第二步重排以後,線程1執行完這步(obj = this)後,時間片分給第二個線程執行,那麼線程2將會獲取final域初始化以前的值,這確定就違背了程序的初衷。

相關文章
相關標籤/搜索