本文首發位址 https://www.dgjava.com/archives/javavolatile01
volatile在Java并發程式設計中常用于保持記憶體可見性和防止指令重排序
這句話很簡單,看起來很好了解,但似乎又不好了解,是以我們幹脆通過代碼來說明。
/**
* <p>深入了解volatile關鍵字<p/>
*
* @Author: 地瓜(yangxw)
* @Date: 2020/4/8 12:55 AM
*/
public class VolatileTest {
final static int MXA = 5;
static int init_value = 0;
public static void main(String[] args) {
new Thread(() -> {
int local_value = init_value;
while (local_value < MXA) {
if (init_value != local_value) {
System.out.printf("init_value 更新為[%d]\n", init_value);
local_value = init_value;
}
}
}, "reader").start();
new Thread(() -> {
int local_value = init_value;
while (local_value < MXA) {
++local_value;
System.out.printf("init_value 變為[%d]\n", local_value);
init_value = local_value;
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "update").start();
}
}
這是一段很簡單的代碼,main方法中啟動兩個線程。第一個線程比較 init_value 和 local_value 的值,如果不相等,列印最新的 init_value 的值,并更新。第二個線程則對 local_value 做自加操作,并指派給 init_value 。理想狀态下兩個線程同時運作,應該輸出類似 :
- init_value 更新為1
- init_value 變為1
如此交替遞增。然而實際效果如下圖:
可以看到,第一個線程的輸入代碼沒有如預期的那樣實作,那就意味着在第一個線程的while循環中,local_value 首次指派後,init_value 的值一直沒變。實際上真的沒有變嗎?基于以上代碼,我将第一個線程做一個改造,在循環中休眠200ms,并輸出最新的local_value和init_value,代碼如下:
new Thread(() -> {
int local_value = init_value;
while (local_value < MXA) {
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("local_value=[%d],init_value=[%d]\n", local_value, init_value);
if (init_value != local_value) {
System.out.printf("init_value 更新為[%d]\n", init_value);
local_value = init_value;
}
}
}, "reader").start();
實際輸出結果為:
看到這個結果,似乎有些明了。在第一個線程中,并不是init_value沒有發生變化,隻是線程執行太快,沒能及時讀取到init_value的最新值。
在一開始說到過,volatile在Java并發程式設計中常用于保持記憶體可見性和防止指令重排序。那我們不妨在init_value前加上volatile關鍵字,看看是否能在第一個線程中實時讀取到init_value在記憶體中的最新值,我們把休眠的代碼去掉,隻保正常留輸出。代碼如下:
public class VolatileTest {
final static int MXA = 5;
volatile static int init_value = 0;
public static void main(String[] args) {
new Thread(() -> {
int local_value = init_value;
while (local_value < MXA) {
if (init_value != local_value) {
System.out.printf("init_value 更新為[%d]\n", init_value);
local_value = init_value;
}
}
}, "reader").start();
new Thread(() -> {
int local_value = init_value;
while (local_value < MXA) {
++local_value;
System.out.printf("init_value 變為[%d]\n", local_value);
init_value = local_value;
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "update").start();
}
}
再來看看運作結果
通過這個圖,那麼volatile在Java并發程式設計中常用于保持記憶體可見性就很好了解了。同時也可以結果,為什麼volatile關鍵字在高并發場景下,為何那麼好用了。
源碼位址