並發編程之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的作用

  • 保障可見性,有序性和Longdouble類型變量讀寫操作的原子性
  • 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,否則並不能保證對象可見性

參考文章