Android volatile 原理。

在《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);
    }
image.png

這段代碼啟動了 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)存屏障之前的位置。
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容