Java記憶體模型FAQ(九)在新的Java記憶體模型中,final欄位是如何工作的

喝水會長肉發表於2021-12-02

一個物件的final欄位值是在它的構造方法裡面設定的。假設物件被正確的構造了,一旦物件被構造,在構造方法裡面設定給final欄位的的值在沒有同步的情況下對所有其他的執行緒都會可見。另外,引用這些final欄位的物件或陣列都將會看到final欄位的最新值。

對一個物件來說,被正確的構造是什麼意思呢?簡單來說,它意味著這個正在構造的物件的引用在構造期間沒有被允許逸出。(參見安全構造技術)。換句話說,不要讓其他執行緒在其他地方能夠看見一個構造期間的物件引用。不要指派給一個靜態欄位,不要作為一個listener註冊給其他物件等等。這些操作應該在構造方法之後完成,而不是構造方法中來完成。


class 
FinalFieldExample 
{

 final int x ;
 int y ;
  static FinalFieldExample f ;
  public FinalFieldExample ( ) {
   x = 3 ;
   y = 4 ;
  }

  static void writer ( ) {
   f = new FinalFieldExample ( ) ;
  }

  static void reader ( ) {
    if (f != null ) {
     int i = f .x ;
     int j = f .y ;
    }
  }
}

上面的類展示了final欄位應該如何使用。一個正在執行reader方法的執行緒保證看到f.x的值為3,因為它是final欄位。它不保證看到f.y的值為4,因為f.y不是final欄位。如果FinalFieldExample的構造方法像這樣:


public 
FinalFieldExample
(
) 
{ 
// bad!

 x = 3 ;
 y = 4 ;
  // bad construction - allowing this to escape
 global .obj = this ;
}

那麼,從global.obj中讀取this的引用執行緒不會保證讀取到的x的值為3。

能夠看到欄位的正確的構造值固然不錯,但是,如果欄位本身就是一個引用,那麼,你還是希望你的程式碼能夠看到引用所指向的這個物件(或者陣列)的最新值。如果你的欄位是final欄位,那麼這是能夠保證的。因此,當一個final指標指向一個陣列,你不需要擔心執行緒能夠看到引用的最新值卻看不到引用所指向的陣列的最新值。重複一下,這兒的“正確的”的意思是“物件構造方法結尾的最新的值”而不是“最新可用的值”。

現在,在講了如上的這段之後,如果在一個執行緒構造了一個不可變物件之後(物件僅包含final欄位),你希望保證這個物件被其他執行緒正確的檢視,你仍然需要使用同步才行。例如,沒有其他的方式可以保證不可變物件的引用將被第二個執行緒看到。使用final欄位的程式應該仔細的除錯,這需要深入而且仔細的理解併發在你的程式碼中是如何被管理的。

如果你使用JNI來改變你的final欄位,這方面的行為是沒有定義的。

 

原文


How do final fields work under the new JMM?

The values for an object’s final fields are set in its constructor. Assuming the object is constructed “correctly”, once an object is constructed, the values assigned to the final fields in the constructor will be visible to all other threads without synchronization. In addition, the visible values for any other object or array referenced by those final fields will be at least as up-to-date as the final fields.

What does it mean for an object to be properly constructed? It simply means that no reference to the object being constructed is allowed to “escape” during construction. (See Safe Construction Techniques for examples.)  In other words, do not place a reference to the object being constructed anywhere where another thread might be able to see it; do not assign it to a static field, do not register it as a listener with any other object, and so on. These tasks should be done after the constructor completes, not in the constructor.


//java學習交流:737251827  進入可領取學習資源及對十年開發經驗大佬提問,免費解答!

class FinalFieldExample {
 final int x ;
 int y ;
  static FinalFieldExample f ;
  public FinalFieldExample ( ) {
   x = 3 ;
   y = 4 ;
  }

  static void writer ( ) {
   f = new FinalFieldExample ( ) ;
  }

  static void reader ( ) {
    if ( f != null ) {
     int i = f .x ;
     int j = f .y ;
    }
  }
}


The class above is an example of how final fields should be used. A thread executing reader is guaranteed to see the value 3 for f.x, because it is final. It is not guaranteed to see the value 4 for y, because it is not final. If FinalFieldExample‘s constructor looked like this:


public 
FinalFieldExample
(
) 
{ 
// bad!

 x = 3 ;
 y = 4 ;
  // bad construction - allowing this to escape
 global .obj = this ;
}

then threads that read the reference to this from global.obj are  not guaranteed to see 3 for x.

The ability to see the correctly constructed value for the field is nice, but if the field itself is a reference, then you also want your code to see the up to date values for the object (or array) to which it points. If your field is a final field, this is also guaranteed. So, you can have a final pointer to an array and not have to worry about other threads seeing the correct values for the array reference, but incorrect values for the contents of the array. Again, by “correct” here, we mean “up to date as of the end of the object’s constructor”, not “the latest value available”.

Now, having said all of this, if, after a thread constructs an immutable object (that is, an object that only contains final fields), you want to ensure that it is seen correctly by all of the other thread, you  still typically need to use synchronization. There is no other way to ensure, for example, that the reference to the immutable object will be seen by the second thread. The guarantees the program gets from final fields should be carefully tempered with a deep and careful understanding of how concurrency is managed in your code.

There is no defined behavior if you want to use JNI to change final fields.



來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70010294/viewspace-2845417/,如需轉載,請註明出處,否則將追究法律責任。

相關文章