Java的挥发性修饰符
不久前,我編寫了一個Java servlet過濾器,該過濾器在其init函數中加載配置(基于web.xml的參數)。 篩選器的配置緩存在私有字段中。 我在字段上設置了volatile修飾符。
后來,當我檢查Sonar公司以查看是否在代碼中發現任何警告或問題時,得知使用volatile違反了規定,我感到有些驚訝。 解釋為:
通常使用關鍵字“ volatile”來微調Java應用程序,因此需要Java內存模型的專業知識。 而且,它的作用范圍還不太清楚。 因此,volatile關鍵字不應用于維護目的和可移植性。
我同意volatile是許多Java程序員所不知道的。 對于一些甚至未知。 不僅因為它從一開始就沒有使用太多,還因為它的定義自Java 1.5起就發生了變化。
讓我稍微回顧一下Sonar的違規行為,首先解釋volatile在Java 1.5及更高版本中的含義(直到撰寫本文時Java 1.8)。
什么是揮發物?
盡管volatile修飾符本身來自C,但在Java中它具有完全不同的含義。 這可能無助于加深對它的理解,使用谷歌搜索揮發物可能會導致不同的結果。 讓我們快速邁出第一步,看看volatile在C語言中的含義。
在C語言中,編譯器通常假定變量無法自行更改值。 盡管這是默認行為,但有時變量可能表示可以更改的位置(例如硬件寄存器)。 使用易失性變量指示編譯器不要應用這些優化。
回到Java。 C中的volatile的含義在Java中將毫無用處。 JVM使用本機庫與操作系統和硬件進行交互。 此外,將Java變量指向特定地址根本是不可能的,因此變量實際上不會自行更改值。
但是,JVM上變量的值可以由不同的線程更改。 默認情況下,編譯器假定變量不會在其他線程中更改。 因此,它可以應用優化,例如對存儲器操作重新排序以及將變量緩存在CPU寄存器中。 使用易失性變量指示編譯器不要應用這些優化。 這樣可以保證讀取線程始終從內存(或共享緩存)中讀取變量,而不從本地緩存中讀取變量。
原子性
更進一步,關于32位JVM的volatile使寫入到64位可變原子(如long或double )成為可能。 要寫入變量,JVM會指示CPU將操作數寫入內存中的某個位置。 使用32位指令集時,如果變量的大小為64位怎么辦? 顯然,該變量必須用兩條指令(一次32位)寫入。
在多線程方案中,另一個線程可能會在寫入過程中讀取變量。 此時,僅寫入變量的前半部分。 這種爭用條件可以通過可變的方式來防止,從而有效地使對32位體系結構的64位變量進行原子寫入。
請注意,上面我談到的是寫而不是更新 。 使用volatile不會使更新原子化。 例如,當i易失時, ++i將從堆或L3緩存中讀取i的值到本地寄存器inc中,然后將該寄存器寫回到i的共享位置。 在讀寫之間, i可能會被另一個線程更改。 在讀寫指令周圍加一個鎖,使更新成為原子操作。 或更妙的是,使用concurrent.atomic包中原子變量類的非阻塞指令。
副作用
volatile變量在內存可見性方面也有副作用。 當線程讀取volatile變量時,不僅對volatile變量的更改對其他線程可見,而且導致更改的代碼的任何副作用也可見。 或更正式地說,易失性變量與該變量的后續讀取之間建立事前關聯。
即,從內存可見性的角度來看,有效地寫入易失性變量就像退出同步塊并讀取易失性變量就像進入變量一樣。
選擇易失性
回到我使用volatile一次初始化配置并將其緩存在私有字段中的情況。
到目前為止,我相信確保此字段對所有線程可見的最佳方法是使用volatile。 我本來可以使用AtomicReference 。 由于該字段僅被寫入一次(在構造之后,因此不可能是最終的),因此原子變量傳達了錯誤的意圖。 我不想使更新原子化,我想使緩存對所有線程可見。 對于它的價值,原子類也使用volatile。
關于聲納法則的思考
既然我們已經了解了volatile在Java中的含義,那么讓我們進一步討論一下Sonar規則。
在我看來,此規則是Sonar之類的工具配置中的缺陷之一。 如果您需要跨線程共享(可變)狀態,那么使用volatile可能是一件非常好的事情。 當然,您必須將此保持在最低水平。 但是,此規則的結果是,不了解什么是揮發性的人會遵循建議不要使用揮發性。 如果他們有效地刪除修飾符,則會引入競爭條件。
我確實認為使用未知或危險的語言功能時自動升起紅色標記是個好主意。 但是,也許只有當有更好的替代方案來解決同一問題時,這才是一個好主意。 在這種情況下,volatile沒有其他選擇。
請注意,這絕不是對Sonar的指責。 但是,我確實認為人們應該選擇一套他們認為重要的規則來應用,而不是采用默認配置。 我發現使用默認情況下啟用的規則的想法有點天真。 您的項目很有可能不是工具維護者選擇標準配置時考慮的項目。
此外,我相信當您遇到未知的語言功能時,您應該了解它。 當您了解它時,可以決定是否有更好的選擇。
實踐中的Java并發
關于JVM中并發性的事實上的標準書是Brain Goetz編寫的Java Concurrency in Practice 。 它從多個詳細級別解釋了并發的各個方面。 如果您在Java(或不純的Scala)中使用任何形式的并發,請確保至少閱讀本書的前三章,以對問題有一個較高的了解。
翻譯自: https://www.javacodegeeks.com/2014/08/javas-volatile-modifier-2.html
總結
以上是生活随笔為你收集整理的Java的挥发性修饰符的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ddos的肉鸡怎么来(ddos怎么利用肉
- 下一篇: linux占用cpu过高排查(linux