17.5 final 域字段的語義

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)的回答

鏈接1
鏈接2
鏈接3

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就作了說明:

利用final關(guān)鍵字實現(xiàn)不變性設(shè)計
final關(guān)鍵字和不變性原則

不變性的設(shè)計原則

所謂的線程安全和不安全其實就是由于多線程并發(fā)操作對象的狀態(tài),導(dǎo)致對象的狀態(tài)出現(xiàn)不一致。

利用不變性來保證線程安全:

可變對象 VS 不可變對象

"
如果一個對象的狀態(tài)不能改變,那么它永遠不會遇到由于多個操作以不同方式改變其狀態(tài)而導(dǎo)致的沖突和不一致現(xiàn)象。
"

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 多線程之 Final變量 詳解 原文: http://www.tuicool.com/articles/2Yjmq...
    朦朧蜜桃閱讀 1,458評論 0 2
  • 從三月份找實習(xí)到現(xiàn)在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發(fā)崗...
    時芥藍閱讀 42,803評論 11 349
  • 在一個方法內(nèi)部定義的變量都存儲在棧中,當(dāng)這個函數(shù)運行結(jié)束后,其對應(yīng)的棧就會被回收,此時,在其方法體中定義的變量將不...
    Y了個J閱讀 4,573評論 1 14
  • 源于一次老鄉(xiāng)會和推免師姐的交流,大二的我決定開始尋找自己能堅持的事,剛好看到了日更挑戰(zhàn),希望今后可以堅持,也希望有...
    icy996閱讀 216評論 0 0
  • 轉(zhuǎn)眼元宵,過了元宵,這個年便也算是翻過了。今年與往年別樣,在與病床的纏斗中,于這個清靜的年中,感悟到了平日里...
    小戴子2號閱讀 459評論 0 2

友情鏈接更多精彩內(nèi)容