Java雜談5——關鍵字final與volatile


Final關鍵字

  在Java語言中,隨着語境的不同final關鍵字所代表的語義會有一些細微的差異。總的來說,final關鍵字表達的含義是“禁止修改”,這層有點類似於C++中的const關鍵字。之所以要采用final關鍵字,一般是會出於性能和設計層面的考慮。下文會具體討論final關鍵字在不同語境中的具體用法。

Final + 屬性

  用final關鍵字修飾的屬性,對於Java編譯器來說就是一個“常量”。其特點是:1.具體的值在編譯期間就已經被確定;2.在運行時不能再被修改。基於以上兩個特點,我們分別分析一下Java中具體的基本類型和引用類型:

  對於基本類型,其本身就存放於虛擬機棧內部,由於這些基本類型都是與底層數據類型直接對應的,一些確定的計算過程可以直接在編譯期完成,優化了運行期的執行效率。

  對於引用類型,我們已知引用本身其實也是存放於虛擬機棧中,final關鍵字只限制了對這個引用的更改,並不會限制對引用所指的實例化對象的變更。

綜上所述,我們可以看出,final關鍵在修飾屬性時,不論屬性類型,限制修改的范圍是固定在虛擬機棧內部的存儲。

空白(blank)final

  一個final屬性可以定義的時候不賦予初始的值,但是在其實際使用之前必定需要被初始化,通常final屬性的初始化,只會位於構造函數中或者屬性定義時的表達式表達式。

Final+方法

  如果一個方法如果被聲明為final類型,包含了兩層含義:1.這個方法不能再被重寫(即方法本身不能再被修改);2.方法的調用過程采用內嵌機制,更為高效(節約了入棧出棧的開銷,更為高效,此時的final語義上有點類似於c++中的inline函數)。

  需要注意的是,private關鍵字是不能和final連用的。如果一個父類包含了private final修飾的方法,根據private關鍵字的語義,子類中應該是可以重新定義一個與父類中對應方法簽名一直的方法,但是final關鍵字的語義有說明了這個方法是不能被重寫的,產生了歧義。

Final修飾的形參

  如果一個方法的形參用final關鍵字修飾,表示的含義就是在這個方法內部,這個形參的值是不能被修改的。有點類似於final屬性,如果final修飾的形參數據類型是一個引用,這里只限制了這個引用本身被更改,並不會限制對引用指向實例化對象的修改。

Final+類

         一個類用final關鍵字修飾,其含義包括:1.這個類不能再被任何子類繼承,2.這個類內部的所有方法都默認是final方法


 

Volatile關鍵字

  Volatile的英文含義為易變的、揮發的,在java語言中volatile關鍵字是一個類型修飾符。JAVA中Volatile的作用:強制每次都直接讀內存,阻止重排序,確保voltile類型的值一旦被寫入緩存必定會被立即更新到主存

要真正理解volatile語義的作用,就必須優先搞懂volatile關鍵字相關的幾個概念。

Java內存/線程模型

  在JAVA多線程環境下,每個Java線程除了共享的虛擬機棧外和Java堆之外,還存在一個獨立私有的堆內存(默認情況下大小為512KB,在線程被創建時分配,可以通過-Xss選項調節其默認值大小)。每個線程獨立運行,彼此之間都不可見,線程的私有堆內存中保留了一份主內存的拷貝,只有在特定需求的情況下才會與主存做交互(復制/刷新)。

JAVA內存模型示例圖

重排序

         在有些場景下訪問程序變量會表現出與程序制定的順序不一樣。例如:編譯器可以以優化的名義改變指令的順序,處理器也可以不按照順序來執行指令等。一個Java程序在從源代碼到最終實際執行的指令序列之間,會經歷一系列的重排序過程:

  這種任意改變指令順序的行為在單線程的情況下“似乎”是無害有益的,但是如果換到多線程的情況,由於所有線程都獨立運行,不知道其他線程在做什么;如果多個線程批次之間還存在共享的內存區域,就必須解決同步的問題。

Volatile使用的理想條件

  正是由於JAVA中volatile關鍵字的這種語義,volatile可以被認為是一個輕量級的synchronized塊,但實際使用過程中還是有一些限制。

  Volatile被正確使用的理想條件包括如下兩點:

  • 對變量的寫操作不依賴當前值。
  • 變量沒有包含在具有其他變量的不變式中。

  說得很抽象,歸結起來就是一個volatile變量能夠用來實現“輕量級的同步”,前提是這個變量自身不能同時被多個線程共享修改。

  通常volatile的使用場景:存在多個線程同時運行,只有一個線程擁有對volatile屬性的修改權,其他的線程只能進行讀操作。具體的示例參考模式有興趣的同學可以參考文章《JAVA理論與實踐》。

Volatile的注意事項

         Volatile只能保證每次線程讀取到的變量來自最新的內存,保證修改操作能夠及時反饋到主內存中,相對於synchronized鎖定內存的做法,volatile在讀取時擁有很大的性能上的優勢。

Voltile關鍵字並不保證操作的原子性,例如一個自增操作i++,即使i被聲明為voltile類型,在多線程的情況下,i的值也會是不確定的。

         在JAVA中long類型和double類型都是64位長度,對於32位的機器這需要兩次讀內存的操作。在規范中,JAVA內存模型只在這種情況下保證用voilate修飾的double和long類型變量的兩次讀操作變為一個原子操作。

  綜上所述,volatile關鍵字表現出了一種脆弱的同步機制,在無法確認當前場景絕對滿足的前提下,voilate關鍵字很難保證安全合理得被使用。但確實volatile為我們提供了在特定場景下更高的性能(相比synchronized)。

參考資料

  1. Java Memory Model http://www.cs.umd.edu/~pugh/java/memoryModel/
  2. 《Thinking in JAVA》
  3. JAVA理論與實踐:正確使用volatile變量http://www.ibm.com/developerworks/cn/java/j-jtp06197.html
  4. JSR 135 (JAVA Memory Model)FAQhttp://www.ticmy.com/?p=315
  5. JAVA線程/內存模型的缺陷與增強http://tech.163.com/06/0525/09/2HV7DCJ20009159T.html
  6. Wekipedia Java Memory Modelhttp://en.wikipedia.org/wiki/Java_Memory_Model
  7. 深入理解Java內存模型http://www.infoq.com/cn/articles/java-memory-model-1


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM