volatile(揮發性) 在Java裡的概念與使用時機

我與volatile的第一次見面

當時正在Android OS上porting CAN bus protocol,
從網路上找來了code,但收到的data透過層層傳遞後,最後總是收到亂碼。

後來用print大法一層一層的看data值,
很驚訝地發現! 在native library那一層,
一開始收到的data是對的,但return給下一層的時候data值就變了 !

如果在這中間加上printf印出data值,或是將data設為volatile型態,
data值就不會被莫名改變

當時對volatile的概念還是懵懵懂懂,
最近念書又念到相關的用法,
在這裡做個紀錄,方便以後參考。

volatile廣義簡介

參考Wiki百科可以知道volatile關鍵字的定義:
[http://zh.wikipedia.org/wiki/Volatile%E5%8F%98%E9%87%8F]

"使用volatile關鍵字聲明的變量或對象通常擁有和優化和(或)多執行緒相關的特殊屬性。通常,volatile關鍵字用來阻止(偽)編譯器對某些其認為無法「被代碼本身」改變的代碼(變量/對象)進行優化。"

以上提到了volatile會阻止某種"優化",再往下看這個優化的實際內容是什麼:

"編譯器可能優化讀取和存儲,可能暫時使用暫存器中的值,如果這個變量由別的程序更新了的話,將出現不一致的現象。"

也就是說,volatile關鍵字會阻止使用暫存器數值這種"偷懶"的優化行為,以確保變數數值的一致性。

以下會以Java為情境,更進一步舉例以及說明何時該使用這個機制。

volatile in JAVA

在"Practical Java : Programming Language Guide"一書中,記錄了Java撰寫的其中一項原則:
「取用共享變數時請使用synchronized或volatile。」

我將裡面的例子節錄於下:

class TimeClock {
    private int mID;
    private long mTime;

    public int getID() {
        return mID;
    }

    public void setID(int id) {
        mID = id;
    }
    ...
}
volatile廣義簡介V
當我們創立TimeClock物件,其變數mID以及mTime會儲存在main memory(主記憶體)。但Java語言為了提高程式執行效率,允許執行緒保存變數的private working copy(專用副本)。只有在特定的synchronization point(同步點),private working copy才會更新main memory。

假設我們創立了兩個執行緒去存取剛剛建立的TimeClock物件,且進行了以下的event sequence:

    1. Thread 1 呼叫 setID(5);
    2. Thread 2 呼叫 setID(10);
    3. Thread 1 呼叫 getID(); 結果程式回傳數值5。

在事件3呼叫getID時,我們預期得到的是最新的ID數值,也就是被Thread2寫入的10。但由於Thread1是從自己的working copy中讀取數值,所以我們得到5。

如果我們將mID宣告為volatile,就能保證變數在main memory與working copy的數值一致。且Thread1和Thread2可以同時執行。

把該變數聲明為volatile就指示JVM這個變數是不穩定的,例如這是一個會被多執行緒存取的共享變數,則每次使用它都需要到主存中進行讀取,才能得到最新的數值。而一致化的動作就在每次取用這個變數的時候發生。
volatile廣義簡介

留言