17.5 final 域字段的語義
被final聲明的域字段只能初始化一次,并且它們永遠不會發(fā)生變化。 final域字段的語義細節(jié)和一般的域字段有點不同。特別地,編譯器可以自由地把跨過個同步屏障器和同步調(diào)用的對final域字段的讀取轉(zhuǎn)移至任意的或未知的方法。相應(yīng)地,也允許編譯把一個final域字段的值緩存在寄存器中并且不必從內(nèi)存中重新加載它。(而在這些情況下,一個non-final域字段卻不得不被重新加載)。
final域的存在使得程序員可以在沒有使用同步的情況,實現(xiàn)一個線程安全的不可變對象。一個線程安全的不可變對象對所有線程來說,都是不可變的,即使有一個數(shù)據(jù)競爭把一個對不可變對象的引用在線程間傳遞。這就為錯誤或惡意代碼對不可變類的錯誤使用提供了一個安全保障。 final域必須正確使用才能為不可變提供保證。
只有當(dāng)一個對象的構(gòu)造方法執(zhí)行完成后,才認為該對象已經(jīng)完全初始化了。如果一個線程,它,只有當(dāng)一個對象已經(jīng)完全被初始化之后,該線程才可以看到對此對象的引用。那么可以保證的是,這個線程看到的對象的final域肯定是已被正確初始化后的。
final域的使用模型非常簡單: 在對象的構(gòu)造方法中對這個對象的final域進行設(shè)置,并且如果另一個線程可以在對象的構(gòu)造器執(zhí)行結(jié)束之前看到該對象,那么在這樣的地方,不要寫入對該對象的引用。如果遵循這樣的設(shè)計,那么當(dāng)一個對象被另一個線程看到時,這個線程看到的對象final域?qū)⒖偸且呀?jīng)被正確初始化后的版本。而且,該線程看到的被final域字段引用的對象或數(shù)組的版本也將和final域一樣是最新的。
Example 17.5-1 java 內(nèi)存模型中的final域
代碼示例
public class FinalFieldExample {
final int x;
int y;
static FinalFieldExample f;
public FinalFieldExample() { //一個線程安全的不可變對象對于所有線程來說都是不可變的。
x=3;
y=4;
}
static void writer() {
f=new FinalFieldExample(); //在這個構(gòu)造函數(shù)完成之前,reader線程能看到嗎???
}
static void reader() { //在線程間傳遞不可變對象的引用
if(f !=null) {
int i=f.x; // guaranteed to 3
int j=f.y; // could see 0 為什么可以看到零???
}
}
}
FinalFieldExample類有一個final int 域字段x和一個non-final int 域字段y。現(xiàn)在假如有一個線程執(zhí)行方法writer()并且另一個線程執(zhí)行reader()。因為writer()方法會在對象的構(gòu)造方法結(jié)束之后,向f寫入值,所以可以保證的是reader()方法將看到的是已經(jīng)正確初始化的f.x: 它讀取到的值會是3。但是,f.y不是final域字段,因此,無法保證reader方法會讀到值為4的y。
這里補充說明一下,為什么讀取到的y值可能是0,可以點擊下面的三個鏈接,了解相關(guān)的回答
Example 17.5-2 使用final域來保證安全
final 域可以被設(shè)計用來做一些安全保障。
設(shè)想一下下面的程序。有一個線程thread 1執(zhí)行下面的代碼:
Global.s= "/tmp/usr".substring(4);
然而此時,另一個線程thread2執(zhí)行:
String myS= Global.s;
if(myS.equals("/tmp"))
System.out.println(myS);
String對象都是不可變的,相關(guān)的字符串操作也不會執(zhí)行同步操作。雖然String實現(xiàn)沒有任何數(shù)據(jù)競爭,但是涉及到使用String對象的代碼卻可能存在數(shù)據(jù)競爭,并且內(nèi)存模型對那些包含數(shù)據(jù)競爭的程序只做了很少的保證。
如果String類的域字段不是final,就有可能出現(xiàn)這樣的情況: 線程2最初看到的該string對象的起點值很可能是默認值0。并允許它去和"/tmp"做比較。之后在String對象上的操作可能看到的是正確的起點值4,因此 那個String對象被視為"/usr"。 java語言的許多安全特性都取決于String對象要被視為是真正地不可變的,即使惡意代碼使用數(shù)據(jù)競爭在線程間傳遞String的引用。
個人總結(jié): final域的特點是:一個對象的final域一定會被正確地初始化,但普通的域字段卻不一定。
上述全部是jls8上的內(nèi)容
final補充
為什么final可以實現(xiàn)線程安全???
在《java并發(fā)編程設(shè)計原則與模式》中Doug lea就final就作了說明:


不變性的設(shè)計原則
所謂的線程安全和不安全其實就是由于多線程并發(fā)操作對象的狀態(tài),導(dǎo)致對象的狀態(tài)出現(xiàn)不一致。
利用不變性來保證線程安全:
可變對象 VS 不可變對象
"
如果一個對象的狀態(tài)不能改變,那么它永遠不會遇到由于多個操作以不同方式改變其狀態(tài)而導(dǎo)致的沖突和不一致現(xiàn)象。
"