Java中的Volatile關鍵字

問題根源

在多線程運行場景中,每個線程可能運行與 不同的CPU中,每個線程的讀寫都是操作自己CPU所在的高速緩存,但操作的數據存儲在堆中,作爲共享變量,每個線程操作的實際上是自己本地線程的副本,在併發 編程中,可能就會存在緩存不一致的問題

早期的CPU是通過總線加Lock#鎖的方式解決緩存不一致問題,對總線加Lock#鎖只保證只能有一個CPU使用這個變量的內存,這樣會導致其他CPU無法訪問內存,效率低,現在主要是通過緩存一致性協議來解決緩存一致性問題

Volatile特徵

  • Volatile保證了不同線程對該線程操作的內存可見性
  • 禁止編譯器進行指令性重排
  • volatile變量是一種輕量同步機制,訪問volatile不會執行加鎖操作
  • volatile無法同事保證內存可見性和 原子性,因此更建議使用加鎖(同步)機制

Volatile解決內存可見性問題

之前我們分析過 ,多線程的讀寫並不會操作主內存(共享內存),而是操作線程的本地副本,這也是導致線程間數據不可見的本質,Volatile主要是從這個角度解決問題

  1. 修改volatile修飾的變量,CPU會強制將修改後的值刷新到主內存中
  2. 修改volatile修飾的變量,會導致其他線程 的本地內存的對應該變量值失效,該變量值的獲取需要再重新從共享內存中讀取

如何防止編譯器指令性重排

編譯器語義重排的後果

編譯器遵守as-if-serial 語義,編譯器和處理器不會對存在數據依賴關係的操作做重排序,因爲這種重排序會改變執行結果。但是,如果操作之間不存在數據依賴關係,這些操作可能被編譯器和處理器重排序。

class ReorderExample {
int a = 0;
boolean flag = false;

public void writer() {
    a = 1;                   //1
    flag = true;             //2
}

Public void reader() {
    if (flag) {                //3
        int i =  a * a;        //4
        ……
    }
}
}  

如圖所示,操作1和操作2做了重排序。程序執行時,線程A首先寫標記變量 flag,隨後線程 B 讀這個變量。由於條件判斷爲真,線程 B 將讀取變量a。此時,變量 a 還根本沒有被線程 A 寫入,在這裏多線程程序的語義被重排序破壞了!在這裏插入圖片描述

volatile關鍵字保證有序性

* volatile關鍵字關鍵字實際上給CPU添加了一個內存屏障,它確保指令重排序時不會把其後面的指令排到內存屏障之前的位置,也不會把前面的指令排到內存屏障的後面;即在執行到內存屏障這句指令時,在它前面的操作已經全部完成

volatile使用要點

  1. 對變量的寫入操作不依賴變量的當前值,或者你能確保只有單個線程更新變量的值。

  2. 該變量沒有包含在具有其他變量的不變式中。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章