C#中volatile的用法
C#中volatile的用法
恐怕比較一下volatile和synchronized的不同是最容易解釋清楚的。volatile是變數修飾符,而synchronized則作用於一段程式碼或方法;看如下三句get程式碼:
- inti1;intgeti1(){returni1;}
- volatileinti2;intgeti2(){returni2;}
- inti3;synchronizedintgeti3(){returni3;}
geti1()得到儲存在當前執行緒中i1的數值。多個執行緒有多個i1變數拷貝,而且這些i1之間可以互不相同。換句話說,另一個執行緒可能已經改 變了它執行緒內的i1值,而這個值可以和當前執行緒中的i1值不相同。事實上,Java有個思想叫“主”記憶體區域,這裡存放了變數目前的“準確值”。每個執行緒 可以有它自己的變數拷貝,而這個變數拷貝值可以和“主”記憶體區域裡存放的不同。因此實際上存在一種可能:“主”記憶體區域裡的i1值是1,執行緒1裡的i1值 是2,執行緒2裡的i1值是3——這線上程1和執行緒2都改變了它們各自的i1值,而且這個改變還沒來得及傳遞給“主”記憶體區域或其他執行緒時就會發生。
而geti2()得到的是“主”記憶體區域的i2數值。用volatile修飾後的變數不允許有不同於“主”記憶體區域的變數拷貝。換句話說,一個變數經 volatile修飾後在所有執行緒中必須是同步的;任何執行緒中改變了它的值,所有其他執行緒立即獲取到了相同的值。理所當然的,volatile修飾的變數 存取時比一般變數消耗的資源要多一點,因為執行緒有它自己的變數拷貝更為高效。
既然volatile關鍵字已經實現了執行緒間資料同步,又要 synchronized幹什麼呢?呵呵,它們之間有兩點不同。首先,synchronized獲得並釋放監視器——如果兩個執行緒使用了同一個物件鎖,監 視器能強制保證程式碼塊同時只被一個執行緒所執行——這是眾所周知的事實。但是,synchronized也同步記憶體:事實上,synchronized在“ 主”記憶體區域同步整個執行緒的記憶體。因此,執行geti3()方法做了如下幾步:
1. 執行緒請求獲得監視this物件的物件鎖(假設未被鎖,否則執行緒等待直到鎖釋放)
2. 執行緒記憶體的資料被消除,從“主”記憶體區域中讀入(Java虛擬機器能優化此步。。。[後面的不知道怎麼表達,汗])
3. 程式碼塊被執行
4. 對於變數的任何改變現在可以安全地寫到“主”記憶體區域中(不過geti3()方法不會改變變數值)
5. 執行緒釋放監視this物件的物件鎖
因此volatile只是在執行緒記憶體和“主”記憶體間同步某個變數的值,而synchronized通過鎖定和解鎖某個監視器同步所有變數的值。顯然synchronized要比volatile消耗更多資源。
更通俗的解釋:
Volatile字面的意思時易變的,不穩定的。在C#中也差不多可以這樣理解。
編譯器在優化程式碼時,可能會把經常用到的程式碼存在Cache裡面,然後下一次呼叫就直接讀取Cache而不是記憶體,這樣就大大提高了效率。但是問題也隨之而來了。
在多執行緒程式中,如果把一個變數放入Cache後,又有其他執行緒改變了變數的值,那麼本執行緒是無法知道這個變化的。它可能會直接讀Cache裡的資料。但是很不幸,Cache裡的資料已經過期了,讀出來的是不合時宜的髒資料。這時就會出現bug。
用Volatile宣告變數可以解決這個問題。用Volatile宣告的變數就相當於告訴編譯器,我不要把這個變數寫Cache,因為這個變數是可能發生改變的。