final 關鍵字與安全發佈 多線程中篇(十三)

final的一般理解

在Java中,final關鍵字能夠用來修飾類、方法和變量(包括成員變量和局部變量)
你們應該都知道final表示最終的、最後的含義,也就是不能在繼續
修飾類表示不能繼承,修飾方法表示不能重寫,修飾變量表示不能修改
image_5c6f5971_3826
當用final修飾一個類時,代表這個類不能被繼承。也就是說,若是一個類你永遠不會讓他被繼承,就能夠用final進行修飾
注意:final類中的全部成員方法都會被隱式地指定爲final方法(也能夠認爲不可以繼承就是由於全部的方法你都不能繼承,因此所有方法隱式final)
當final修飾方法時,表示此方法不能被重寫,也就是不能被子類覆蓋(可是絕不影響多個重載的final方法,重載和重寫不是一個概念)
注意:
若是是final而且private的方法,子類是看不到private的,因此若是子類新寫了一個看似「重寫」的方法,實際上是屬於子類的新方法,這並非重寫了final方法
當final修飾變量時,至關於一個只讀變量,只能進行讀取,而不能進行設置,若是是成員變量那麼須要在賦值時或者構造方法中對他進行設置。
局部變量必須是定義時,參數列表中的就是參數傳遞時,其餘時候不能再進行更改了
綜上,final的一般認知就是這些,表示最終的、最後的、不可變得,能夠用於定義類、方法、變量
 
其實final還有另外的做用,那就是安全發佈對象的一種方法
什麼是安全發佈?

安全發佈

兩個關鍵字「發佈」「安全」
所謂發佈通俗一點的理解就是建立一個對象,使這個對象能被當前範圍以外的代碼所使用
好比Object o = new Object();
而後接下來使用對象o
可是對於普通變量的建立,以前分析過,大體分爲三個步驟:
  • 分配內存空間
  • 將o指向分配的內存空間
  • 調用構造函數來初始化對象
這三個步驟不是原子的,若是執行到第二步,尚未進行初始化,此時對象已經不是null了,若是被其餘代碼訪問,這將收穫一個錯誤的結果。
或者說對象還沒有徹底建立就被使用了,其餘線程看到的結果多是不一致的
這就是 不安全的發佈
根本緣由就是JVM建立對象的過程涉及到分配空間、指針設置、數據初始化等步驟,並非同步的,涉及到主存與緩存、處理器與寄存器等,可見性沒辦法獲得保障
 
因此說,什麼是安全發佈,簡單理解就是對象的建立可以保障在被別人使用前,已經完成了數據的構造設置,或者說一個對象在使用時,已經完成了初始化。
不幸的是,Java對此並無進行保障,你須要本身進行保障,好比synchronized關鍵字,原子性、排他性就能夠作到這一點
怎麼保障安全發佈?有幾種方法:
一種是剛纔提到的鎖機制,經過加鎖能夠保障中間狀態不會被讀取
另外還有:
  • 藉助於volatile或者AtomicReference聲明對象
  • 藉助於final關鍵字
  • 在靜態初始化塊中,進行初始化(JVM會保障)
很顯然,對於鎖機制,那些線程安全的容器好比ConcurrentMap,也是知足這條的,因此也是安全發佈  

final與安全發佈

對於final,當你建立一個對象時,使用final關鍵字可以使得另外一個線程不會訪問處處於「部分建立」的對象
由於:當構造函數退出時,final字段的值保證對訪問構造對象的其餘線程可見
若是某個成員是final的,JVM規範作出以下明確的保證:
一旦對象引用對其餘線程可見,則其final成員也必須正確的賦值
因此說藉助於final,就如同你對對象的建立訪問加鎖了通常,自然的就保障了對象的安全發佈。
若是你不但願後續被繼承、重寫、更改,你應該儘量的將他們聲明爲final
一篇很不錯的文章:
對於普通的變量,對象的內存空間分配、指針設置、數據初始化,和將這個變量的引用賦值給另外一個引用,之間是可能發生重排序的,因此也就致使了其餘線程可能讀取到不一致的中間狀態
可是對於final修飾的變量,JVM會保障順序
不會在對final變量的寫操做完成以前,與將變量引用賦值給其餘變量之間進行重排序,也就是final變量的設置完成始終會在被讀取以前  

總結

final除了不可變的定義以外,還與線程安全發佈息息相關
藉助於final,能夠達到對象安全發佈的保障,只須要藉助於final,不在須要任何額外的付出,他可以保障在多線程環境下,老是可以讀取到正確的初始化的值
因此,若是你不但願變量後續被修改,你應該老是使用final關鍵字
並且,很顯然在某些場景下,final也能夠解決必定的安全問題
相關文章
相關標籤/搜索