1.final域的重排序規則:
1.在構造函數內對一個final域的寫入,與隨后把這個被構造對象的引用賦值給一個引用變量,這兩個操作之間不能重排序。
2.初次讀一個包含final域的對象的引用,與隨后初次讀這個final域,這兩個操作之間不能重排序。
2.寫final域的重排序規則:
寫final域的重排序規則禁止把final域的寫重排序到構造函數之外。這個規則的實現包含下面2個方面。
? ?1)JMM禁止編譯器把final域的寫重排序到構造函數之外。
? ?2)編譯器會在final域的寫之后,構造函數return之前,插入一個StoreStore屏障。這個屏障禁止處理器把final域的寫重排序到構造函數之外。
寫final域的重排序規則可以確保:在對象引用為任意線程可見之前,對象的final域已經被正確初始化過了,而普通域不具有這個保障。
3.讀final域的重排序規則:
讀final域的重排序規則是,在一個線程中,初次讀對象引用與初次讀該對象包含的final域,JMM禁止處理器重排序這兩個操作(注意,這個規則僅僅針對處理器)。編譯器會在讀final域操作的前面插入一個LoadLoad屏障。
初次讀對象引用與初次讀該對象包含的final域,這兩個操作之間存在間接依賴關系。由于編譯器遵守間接依賴關系,因此編譯器不會重排序這兩個操作。大多數處理器也會遵守間接依賴,也不會重排序這兩個操作。但有少數處理器允許對存在間接依賴關系的操作做重排序(比如alpha處理器),這個規則就是專門用來針對這種處理器的。
讀final域的重排序規則可以確保:在讀一個對象的final域之前,一定會先讀包含這個final域的對象的引用。
4.final域為引用類型:
對于引用類型,寫final域的重排序規則對編譯器和處理器增加了如下約束:在構造函數內對一個final引用的對象的成員域的寫入,與隨后在構造函數外把這個被構造對象的引用賦值給一個引用變量,這兩個操作之間不能重排序。
這一規則確保了其他線程能讀到被正確初始化的final引用對象的成員域。
5.為什么final引用不能從構造函數內“溢出”:
寫 final 域的重排序規則可以確保:在引用變量為任意線程可見之前,該引用變量指向的對象的 final 域已經在構造函數中被正確初始化過了。其實要得到這個效果,還需要一個保證:在構造函數內部,不能讓這個被構造對象的引用為其他線程可見,也就是對象引用不能在構造函數中“逸出”。
在構造函數返回前,被構造對象的引用不能為其他線程可見,因為此時的 final 域可能還沒有被初始化。在構造函數返回后,任意線程都將保證能看到 final 域正確初始化之后的值。
通過為 final 域增加寫和讀重排序規則,可以為 java 程序員提供初始化安全保證:只要對象是正確構造的(被構造對象的引用在構造函數中沒有“逸出”),那么不需要使用同步(指 lock 和 volatile 的使用),就可以保證任意線程都能看到這個 final 域在構造函數中被初始化之后的值。