Volatile in Java -- 用volatile解決可視性問題
Overview
volatile是基礎但有時會遭誤解的關鍵字。本篇文章將簡述其在Java語言中的特性、介紹其和同樣用來處理執行緒安全的關鍵字synchronized有何不同,並佐以簡易的範例code做說明。
When to use volatile
在Java裡,每個執行緒有各自的記憶體空間(working memory),當執行完一段操作後,執行緒會再將剛才使用到的變數的值更新到主記憶體(main memory)裡。其他執行緒則可從主記憶體讀取到變數的最新值。
上述特性加快了程式處理的效率,但在多執行緒環境裡卻可能為我們帶來變數可視性(visibility)的問題。即當一個變數的讀取和寫入發生在不同的執行緒時,讀取變數的執行緒有時無法及時看到變數的值的改變(被其他執行緒寫入),導致資料不一致。
此時,可在變數前加上volatile,此變數會改為不使用各執行緒的working memory,永遠從主記憶體做存取與讀寫。
example:
底下的範例程式碼將比較有volatile和沒volatile的差別。
public class VisibilityProblem {
static int num;
public static void main (String[] args) {
Thread readerThread = new Thread(() -> {
int temp = 0;
while (true) {
if (temp != num) {
temp = num;
System.out.println("reader: value of num = " + num);
}
}
});
Thread writerThread = new Thread(() -> {
for (int i = 0; i < 5; i++) {
num++;
System.out.println("writer: changed value to = " + num);
// 進入睡眠,以讓readerThread有足夠時間讀到int num的改變(因為num++非具原子性的操作,readerThread仍有一定機會讀到錯誤的值)
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//離開程式,否則readerThread會一直等待變數int num的值改變
System.exit(0);
});
readerThread.start();
writerThread.start();
}
}
上段程式碼裡,int num為readerThread和writerThread的共用變數,未加volatile修飾的output如下:
沒加volatile時readerThread只讀到變數num從default值0到1的變化。
但將變數num加上volatile修飾子後。即:
static volatile int num;
output變成:
可見加上volatile後,變數num值的變化可及時被readerThread讀取到。
volatile vs synchronized
撰寫多執行緒應用程式時,確保資料一致性的兩大原則:
l Mutual Exclusion - critical section裡的程式碼一次只能被一個執行緒執行
l Visibility – 共享資料的值被某執行緒更改時,其他執行緒可及時看見
使用volatile可確保Visibility,但不具Mutual Exclusion。
使用synchronized,則可保證以上兩項特性,代價則是更差的效能。
用volatile可以幫助我們寫出更簡潔的code。相較用synchronized鎖住某個區塊,因為用volatile像是將同步責任交給JVM,會比我們自己處理更不容易出錯。但如果宣告為volatile的變數經常被使用的話,可能導致程式的效能不如鎖住整個區塊。
volatile in Java vs C/C++
在Java裡volatile是告訴編譯器變數的值不快取到working memory,讀寫永遠透過main memory。
在C/C++裡volatile則是告訴編譯器不要優化我們所撰寫的code,和我們文章上方所討論的同步問題無關係。
Conclusion:
由上可知,在某些需要可視性,且沒有資料競速(race condition)的情境,像是:我們在某個執行緒裡寫了個變數,用來做旗標;在別的執行緒查看那個變數,並且變數值的寫入並不依據當前的值做更新。
或者,我們其實並不那麼在意資料競速可能帶來的誤差,用synchronized顯得殺雞用牛刀時,我們可以使用volatile來達到我們的目的。
簡言之,當我們需要更多的visibility時,我們可以標記變數為volatile,但它不具備鎖的功能,所以使用上仍須有心理準備可能錯過某些更新。
資料來源:
geeksforgeeks:volatile keyword in Java
baeldung:Guide to the Volatile Keyword in Java
logicbig:Java Memory Model - Visibility problem, fixing with volatile variable
volatile應用在設計模式:
geeksforgeeks:builder pattern in java
geeksforgeeks:singleton design pattern