並發編程之volatile

  • 2019 年 12 月 31 日
  • 筆記

文章目錄

1. 並發編程之volatile

1.1. volatile的作用

1.2. 原理

1.2.1. 保證可見性原理

1.2.2. 保證有序性原理

1.2.3. 總結

1.3. volatile變數的開銷

1.4. 使用場景

1.5. 參考文章

並發編程之volatile

  • volatile稱之為輕量級鎖,保證了可見性和原子性。
  • volatile不會引起上下文切換,因此是輕量級的

volatile的作用

  • 保障可見性,有序性和Long,double類型變數讀寫操作的原子性
  • volatile僅僅能保證對其修飾的變數的寫操作以及讀操作本身的原子性,而這並不表示volatile變數的賦值操作一定具有原子性,例如,如下對volatile修飾的變數count的賦值操作並不是原子操作:count++
    • count++可以分為如下步驟
      • 讀取count的值
      • count+1
      • 將count+1的值賦值給count
    • 如果count是一個共享變數,那個該賦值操作實際上是一個read-modify-write操作。其執行過程中其他執行緒可能已經更新了count的值,因此該操作不具備不可分割性,也就不是原子操作。如果變數count是一個局部變數,那麼該賦值操作就是一個原子操作。
  • 一般而言如果對volatile修飾的賦值操作,其右邊的表達式中只要設計共享變數(包括被volatile修飾的變數本身),那麼這個賦值操作就不是原子操作,此時就需要結合鎖來保證原子性了

原理

保證可見性原理

  • 對volatile修飾的變數的讀操作之前插入一個載入屏障,能夠刷新處理器快取,使其讀取的讀取到的變數都是執行緒更新後的最新值。
  • 對volatile修飾的變數的寫操作(修改)之後插入一個存儲屏障,能夠沖刷處理器快取,保證後續的執行緒讀取到的值是最新的。

保證有序性原理

  • 結合釋放屏障和獲取屏障保證了有序性

總結

  • volatile的寫操作相當於鎖的釋放的效果,java虛擬機會在該操作之前插入一個釋放屏障,並在該操作只有插入一個存儲屏障
    • 釋放屏障禁止了volatile寫操作與該操作之前的任何讀寫操作進行重排序,從而保證了volatile寫操作之前的任何讀寫操作會先於volatile寫操作之前提交,即其他執行緒看到寫執行緒對volatile變數的更新時,寫執行緒在更新volatile變數之前執行的記憶體操作的結果對於度鮮橙必然是可見的。即是保障了有序性
    • 存儲屏障保證了在寫操作之後的更新能夠沖刷處理器快取,使得後續的讀執行緒能夠獲取最新的值
  • volatile的讀操作相當於獲取鎖的效果,Java虛擬機會在該操作之前插入一個載入屏障,並在該操作之後插入一個獲取屏障
    • 載入屏障用於刷新處理器快取區,保證讀取到volatile修飾變數的最新值,保證可見性
    • 獲取屏障禁止volatile讀操作之後的任何讀寫操作與volatile讀操作進行重排序。因此保證了有序性

volatile變數的開銷

  1. volatile變數的讀寫不會導致上下文切換,因此開銷比鎖小
  2. 讀取volatile變數每次都需要從高速快取或者主記憶體中讀取,而無法暫存在暫存器中,因此可能比讀取普通變數的成本要高

使用場景

  1. 使用volatile變數作為狀態標誌。比如volatile int flag=false,其他執行緒會讀取該狀態作為執行某一個操作的依據
  2. 單例模式下的雙重校驗鎖的實現效果,其中必須使用volatile,否則並不能保證對象可見性

參考文章

Tags: