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