很顯然,volatile關鍵字修飾的對象修改後,都能在任何線程中立馬拿到。
但是你是否注意到了volatile使用上面的2不能1必須呢?
1、不能將volatile使用到頻繁更新的值上面
如下圖演示
public static void main(String args[]) throws InterruptedException {
for (int i = 0; i < 20; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
b++;
}
}
}).start();
}
while (Thread.activeCount() > 1) {
Thread.yield();
}
System.out.println(b);
}
按照道理,我們應該最後輸出20000,但是最後輸出的結果總是小於20000,且每次不固定 。 究其原因,就是因爲對於某線程A,雖然每次我再進行b++操作之前,都能拿到內存中最新的值,但是我在進行b++之後是要把數據寫回到內存中的。 而這時候可能內存中的值已經被其他線程增加了好多次
2、volatile 對象不能與其他非volatile對象聯合使用,
例如 c=(volatile b+d);使用C的時候,及時b及時改變了,C值也不會改變
3、jvm指令重排會影響業務的標誌性對象 必須使用volatile
例如
private static boolean b = false;
private static void changeFlag() {
//doSomeThingCost 10 seconds //代碼1
b = true; //代碼2
}
private static void sayHello() {
while (true) {
if(b){
System.out.println("hello");
}
}
}
public static void main(String args[]) throws InterruptedException {
new Thread(new Runnable() {
@Override
public void run() {
changeFlag();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
sayHello();
}
}).start();
}
如上代碼,AB線程基本同時執行,B線程要等A中的代碼1執行完後再執行輸出hello . 但是由於JVM的指令重排,可能會導致代碼2在代碼1之前執行,導致提前輸出hello。
這樣的話,如果是銀行等支付付款業務後續處理邏輯,那影響就太大了 。如果加了volatile,那麼JVM的指令重排永遠不能把代碼1放到代碼2後面
順便說一句,內存更新數據是語句級別的,不是方法級別的
驗證代碼如下,輸出爲hello= false; 第一個Thread執行了changeFlag(),然後run()並沒有執行完,b已經修改爲true了。
private volatile static boolean b = false;
private static void changeFlag() {
b = true;
}
private static void sayHello() {
System.out.println("hello=" + b);
}
public static void main(String args[]) throws InterruptedException {
new Thread(new Runnable() {
@Override
public void run() {
changeFlag();
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
b = false;
}
}).start();
Thread.sleep(1000);
new Thread(new Runnable() {
@Override
public void run() {
sayHello();
}
}).start();
}