Java 理論與實踐: 正確使用 Volatile 變數
volatile 變數使用指南 |
級別: 中級 Brian Goetz (brian.goetz@sun.com), 高階工程師, Sun Microsystems 2007 年 7 月 05 日 Java™ 語言包含兩種內在的同步機制:同步塊(或方法)和 volatile 變數。這兩種機制的提出都是為了實現程式碼執行緒的安全性。其中 Volatile 變數的同步性較差(但有時它更簡單並且開銷更低),而且其使用也更容易出錯。在這期的 Java 理論與實踐 中,Brian Goetz 將介紹幾種正確使用 volatile 變數的模式,並針對其適用性限制提出一些建議。 Java 語言中的 volatile 變數可以被看作是一種 “程度較輕的 鎖提供了兩種主要特性:互斥(mutual exclusion) 和可見性(visibility)。互斥即一次只允許一個執行緒持有某個特定的鎖,因此可使用該特性實現對共享資料的協調訪問協議,這樣,一次就只有一個執行緒能夠使用該共享資料。可見性要更加複雜一些,它必須確保釋放鎖之前對共享資料做出的更改對於隨後獲得該鎖的另一個執行緒是可見的 —— 如果沒有同步機制提供的這種可見性保證,執行緒看到的共享變數可能是修改前的值或不一致的值,這將引發許多嚴重問題。 Volatile 變數具有 出於簡易性或可伸縮性的考慮,您可能傾向於使用 volatile 變數而不是鎖。當使用 volatile 變數而非鎖時,某些習慣用法(idiom)更加易於編碼和閱讀。此外,volatile 變數不會像鎖那樣造成執行緒阻塞,因此也很少造成可伸縮性問題。在某些情況下,如果讀操作遠遠大於寫操作,volatile 變數還可以提供優於鎖的效能優勢。 您只能在有限的一些情形下使用 volatile 變數替代鎖。要使 volatile 變數提供理想的執行緒安全,必須同時滿足下面兩個條件:
實際上,這些條件表明,可以被寫入 volatile 變數的這些有效值獨立於任何程式的狀態,包括變數的當前狀態。 第一個條件的限制使 volatile 變數不能用作執行緒安全計數器。雖然增量操作( 大多數程式設計情形都會與這兩個條件的其中之一衝突,使得 volatile 變數不能像 清單 1. 非執行緒安全的數值範圍類
這種方式限制了範圍的狀態變數,因此將 使用 volatile 變數的主要原因是其簡易性:在某些情形下,使用 volatile 變數要比使用相應的鎖簡單得多。使用 volatile 變數次要原因是其效能:某些情況下,volatile 變數同步機制的效能要優於鎖。 很難做出準確、全面的評價,例如 “X 總是比 Y 快”,尤其是對 JVM 內在的操作而言。(例如,某些情況下 VM 也許能夠完全刪除鎖機制,這使得我們難以抽象地比較 volatile 操作不會像鎖一樣造成阻塞,因此,在能夠安全使用 volatile 的情況下,volatile 可以提供一些優於鎖的可伸縮特性。如果讀操作的次數要遠遠超過寫操作,與鎖相比,volatile 變數通常能夠減少同步的效能開銷。 很多併發性專家事實上往往引導使用者遠離 volatile 變數,因為使用它們要比使用鎖更加容易出錯。然而,如果謹慎地遵循一些良好定義的模式,就能夠在很多場合內安全地使用 volatile 變數。要始終牢記使用 volatile 的限制 —— 只有在狀態真正獨立於程式內其他內容時才能使用 volatile —— 這條規則能夠避免將這些模式擴充套件到不安全的用例。 也許實現 volatile 變數的規範使用僅僅是使用一個布林狀態標誌,用於指示發生了一個重要的一次性事件,例如完成初始化或請求停機。 很多應用程式包含了一種控制結構,形式為 “在還沒有準備好停止程式時再執行一些工作”,如清單 2 所示: 清單 2. 將 volatile 變數作為狀態標誌使用
很可能會從迴圈外部呼叫 這種型別的狀態標記的一個公共特性是:通常只有一種狀態轉換; 模式 #2:一次性安全釋出(one-time safe publication) 缺乏同步會導致無法實現可見性,這使得確定何時寫入物件引用而不是原語值變得更加困難。在缺乏同步的情況下,可能會遇到某個物件引用的更新值(由另一個執行緒寫入)和該物件狀態的舊值同時存在。(這就是造成著名的雙重檢查鎖定(double-checked-locking)問題的根源,其中物件引用在沒有同步的情況下進行讀操作,產生的問題是您可能會看到一個更新的引用,但是仍然會通過該引用看到不完全構造的物件)。 實現安全釋出物件的一種技術就是將物件引用定義為 volatile 型別。清單 3 展示了一個示例,其中後臺執行緒在啟動階段從資料庫載入一些資料。其他程式碼在能夠利用這些資料時,在使用之前將檢查這些資料是否曾經發布過。 清單 3. 將 volatile 變數用於一次性安全釋出
如果 該模式的一個必要條件是:被髮布的物件必須是執行緒安全的,或者是有效的不可變物件(有效不可變意味著物件的狀態在釋出之後永遠不會被修改)。volatile 型別的引用可以確保物件的釋出形式的可見性,但是如果物件的狀態在釋出後將發生更改,那麼就需要額外的同步。 模式 #3:獨立觀察(independent observation) 安全使用 volatile 的另一種簡單模式是:定期 “釋出” 觀察結果供程式內部使用。例如,假設有一種環境感測器能夠感覺環境溫度。一個後臺執行緒可能會每隔幾秒讀取一次該感測器,並更新包含當前文件的 volatile 變數。然後,其他執行緒可以讀取這個變數,從而隨時能夠看到最新的溫度值。 使用該模式的另一種應用程式就是收集程式的統計資訊。清單 4 展示了身份驗證機制如何記憶最近一次登入的使用者的名字。將反覆使用 清單 4. 將 volatile 變數用於多個獨立觀察結果的釋出
該模式是前面模式的擴充套件;將某個值釋出以在程式內的其他地方使用,但是與一次性事件的釋出不同,這是一系列獨立事件。這個模式要求被髮布的值是有效不可變的 —— 即值的狀態在釋出後不會更改。使用該值的程式碼需要清楚該值可能隨時發生變化。 volatile bean 模式適用於將 JavaBeans 作為“榮譽結構”使用的框架。在 volatile bean 模式中,JavaBean 被用作一組具有 getter 和/或 setter 方法 的獨立屬性的容器。volatile bean 模式的基本原理是:很多框架為易變資料的持有者(例如 在 volatile bean 模式中,JavaBean 的所有資料成員都是 volatile 型別的,並且 getter 和 setter 方法必須非常普通 —— 除了獲取或設定相應的屬性外,不能包含任何邏輯。此外,對於物件引用的資料成員,引用的物件必須是有效不可變的。(這將禁止具有陣列值的屬性,因為當陣列引用被宣告為 清單 5. 遵守 volatile bean 模式的 Person 物件
前面幾節介紹的模式涵蓋了大部分的基本用例,在這些模式中使用 volatile 非常有用並且簡單。這一節將介紹一種更加高階的模式,在該模式中,volatile 將提供效能或可伸縮性優勢。 volatile 應用的的高階模式非常脆弱。因此,必須對假設的條件仔細證明,並且這些模式被嚴格地封裝了起來,因為即使非常小的更改也會損壞您的程式碼!同樣,使用更高階的 volatile 用例的原因是它能夠提升效能,確保在開始應用高階模式之前,真正確定需要實現這種效能獲益。需要對這些模式進行權衡,放棄可讀性或可維護性來換取可能的效能收益 —— 如果您不需要提升效能(或者不能夠通過一個嚴格的測試程式證明您需要它),那麼這很可能是一次糟糕的交易,因為您很可能會得不償失,換來的東西要比放棄的東西價值更低。 目前為止,您應該瞭解了 volatile 的功能還不足以實現計數器。因為 然而,如果讀操作遠遠超過寫操作,您可以結合使用內部鎖和 volatile 變數來減少公共程式碼路徑的開銷。清單 6 中顯示的執行緒安全的計數器使用 清單 6. 結合使用 volatile 和 synchronized 實現 “開銷較低的讀-寫鎖”
之所以將這種技術稱之為 “開銷較低的讀-寫鎖” 是因為您使用了不同的同步機制進行讀寫操作。因為本例中的寫操作違反了使用 volatile 的第一個條件,因此不能使用 volatile 安全地實現計數器 —— 您必須使用鎖。然而,您可以在讀操作中使用 volatile 確保當前值的可見性,因此可以使用鎖進行所有變化的操作,使用 volatile 進行只讀操作。其中,鎖一次只允許一個執行緒訪問值,volatile 允許多個執行緒執行讀操作,因此當使用 volatile 保證讀程式碼路徑時,要比使用鎖執行全部程式碼路徑獲得更高的共享度 —— 就像讀-寫操作一樣。然而,要隨時牢記這種模式的弱點:如果超越了該模式的最基本應用,結合這兩個競爭的同步機制將變得非常困難。 與鎖相比,Volatile 變數是一種非常簡單但同時又非常脆弱的同步機制,它在某些情況下將提供優於鎖的效能和伸縮性。如果嚴格遵循 volatile 的使用條件 —— 即變數真正獨立於其他變數和自己以前的值 —— 在某些情況下可以使用 學習
|
相關文章
- Java理論與實踐:正確使用Volatile變數Java變數
- Java理論與實踐:正確使用Volatile變數(轉)Java變數
- js正則理論與實踐JS
- Ceph的正確玩法之Ceph糾刪碼理論與實踐
- [Java併發程式設計(四)] Java volatile 的理論實踐Java程式設計
- 正確理解CAP理論
- Java 理論與實踐: 併發集合類Java
- 理解並正確使用synchronized和volatilesynchronized
- 談理論與實踐
- Java 理論與實踐: 關於異常的爭論Java
- Java 執行緒池的理論與實踐Java執行緒
- https理論與實踐HTTP
- 怎樣正確理解volatile?
- volatile變數與普通變數的區別變數
- 理解RESTful:理論與最佳實踐REST
- 正確使用Java事件通知Java事件
- 三層架構--理論與實踐架構
- Java 理論與實踐: 用動態代理進行修飾Java
- volatile的語義與實踐
- Node直出理論與實踐總結
- 深入解析Rivest Cipher 4:理論與實踐
- 密碼學與密碼安全:理論與實踐密碼學
- Golang實時GC的理論與實踐總結GolangGC
- 論React js的正確開啟方式 管理系統介面模組化實踐ReactJS
- 如何正確規範使用論文腳註
- 理論+實踐解析“IT治理”之模式與原則模式
- 【軟體工程理論與實踐】Homework(四.1)軟體工程
- Java如何正確比較浮點數Java
- Java日誌正確使用姿勢Java
- ThreadLocal的正確使用與原理thread
- 從Monolith到微服務:理論與實踐 - Kent BeckMono微服務
- 淺入淺出深度學習理論與實踐深度學習
- 圖書 "ERP理論 方法與實踐" 目錄
- Oracle10g Data Guard (Standby) 理論與實踐Oracle
- 理論指導實踐薦
- 繫結變數的正確使用,bind peeking,Adaptive Cursor Sharing變數APT
- 使用 Java 8 Optional 的正確姿勢Java
- 如何正確處理nonce