在《Java 并發(fā)編程:核心理論》一文中,我們已經(jīng)提到可見性、有序性及原子性 問題,通常情況下我們可以通過 Synchronized 關鍵字來解決這些個問題,不過 如果對 Synchonized 原理有了解的話,應該知道 Synchronized 是一個較重量級 的操作,對系統(tǒng)的性能有比較大的影響,所以如果有其他解決方案,我們通常都 避免使用 Synchronized 來解決問題。
而 volatile 關鍵字就是 Java 中提供的另一種解決可見性有序性問題的方案。對于 原子性,需要強調(diào)一點,也是大家容易誤解的一點:對 volatile 變量的單次讀/ 寫操作可保證原子性的,如 long 和 double 類型變量,但是并不能保證 i++這種 操作的原子性,因為本質(zhì)上 i++是讀、寫兩次操作。 volatile 也是互斥同步的一種實現(xiàn),不過它非常的輕量級。 volatile 的意義? 線程會一直等待。 可以嘗試獲得鎖,線程可以不用一直等待 鎖狀態(tài) 無法判斷 可以判斷 鎖類型 可重入 不可中斷 非公平 可重入 可判斷 可公平(兩者皆可) 性能 少量同步 大量同步
-
防止 CPU 指令重排序 volatile 有兩條關鍵的語義: 保證被 volatile 修飾的變量對所有線程都是可見的 禁止進行指令重排序 要理解 volatile 關鍵字,我們得先從 Java 的線程模型開始說起。如圖所示:
image.png
Java 內(nèi)存模型規(guī)定了所有字段(這些字段包括實例字段、靜態(tài)字段等,不包括局 部變量、方法參數(shù)等,因為這些是線程私有的,并不存在競爭)都存在主內(nèi)存中, 每個線程會 有自己的工作內(nèi)存,工作內(nèi)存里保存了線程所使用到的變量在主內(nèi) 存里的副本拷貝,線程對變量的操作只能在工作內(nèi)存里進行,而不能直接讀寫主 內(nèi)存,當然不同內(nèi)存之間也 無法直接訪問對方的工作內(nèi)存,也就是說主內(nèi)存是 線程傳值的媒介。
我們來理解第一句話:
保證被 volatile 修飾的變量對所有線程都是可見的 如何保證可見性?
被 volatile 修飾的變量在工作內(nèi)存修改后會被強制寫回主內(nèi)存,其他線程在使用 時也會強制從主內(nèi)存刷新,這樣就保證了一致性。 關于“保證被 volatile 修飾的變量對所有線程都是可見的”,有種常見的錯誤理解: - 由于 volatile 修飾的變量在各個線程里都是一致的,所以基于 volatile 變 量的運算在多線程并發(fā)的情況下是安全的。 這句話的前半部分是對的,后半部分卻錯了,因此它忘記考慮變量的操作是否具有原子性這一問題。
舉個例子:
private volatile int start = 0;
private void volatile Keyword() {
Runnable runnable = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
start++;
}
}
}; for (int i = 0; i < 10; i++) {
Thread thread = new Thread(runnable); thread.start();
} Log.d(TAG, "start = " + start);
}

這段代碼啟動了 10 個線程,每次 10 次自增,按道理最終結果應該是 100,但是 結果并非如此。 為什么會這樣?
仔細看一下 start++,它其實并非一個原子操作,簡單來看,它有兩步:
1、取出 start 的值,因為有 volatile 的修飾,這時候的值是正確的。
2、自增,但是自增的時候,別的線程可能已經(jīng)把 start 加大了,這種情況下就有 可能把較小的 start 寫回主內(nèi)存中。
所以 volatile 只能保證可見性,在不符合以 下場景下我們依然需要通過加鎖來保證原子性:
- 運算結果并不依賴變量當前的值,或者只有單一線程修改變量的值。(要 么結果不依賴當前值,要么操作是原子性的,要么只要一個線程修改變量 的值) - 變量不需要與其他狀態(tài)變量共同參與不變約束 比方說我們會在線程里加 個 boolean 變量,來判斷線程是否停止,這種情況就非常適合使用 volatile。
我們再來理解第二句話。 禁止進行指令重排序 什么是指令重排序?
指令重排序是指指令亂序執(zhí)行,即在條件允許的情況下直接運行當前有能 力立即執(zhí)行的后續(xù)指令,避開為獲取一條指令所需數(shù)據(jù)而造成的等待,通 過亂序執(zhí)行的技術提供執(zhí)行效率。 指令重排序會在被 volatile 修飾的變量的賦值操作前,添加一個內(nèi)存屏障, 指令重排序時不能把后面的指令重排序移到內(nèi)存屏障之前的位置。
