Java修飾一個變量爲volatile後,其就具備2種特性:
1.內存可見性
2.禁止指令重排序優化
一、內存可見性是什麼呢?
volatile是保證共享變量變化可見性的關鍵字。所謂可見性,就是一個變量在多個線程間可見,而volatile保證了共享變量的改變在多線程之間能夠及時的發現。
CPU有多個核(線程),每個核(線程)都有一個叫緩衝區(cache),多線程運行程序時每個核的cache就可能發生不同步的情況。
例如:線程(A和B)在運行程序時,線程(A和B)都會從主內存中copy一份變量副本到各自的緩衝區中。當線程B對變量進行了修改操作,這時線程A因爲已經copy了一份變量副本到自己緩衝區,對於線程B對變量進行了修改,線程A是不知道變量已經修改了。線程A還是從自己的緩衝區取數據。
package thread.com.cn;
import java.util.concurrent.TimeUnit;
public class VolatileTest {
/* volatile */ boolean flag = true;
void m() {
System.out.println("m start...");
while (flag) {
}
System.out.println("m end...");
}
public static void main(String[] args) {
VolatileTest t = new VolatileTest();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.flag = false;
}
}
運行結果:
1.CPU非空間情況下就會一直打印m start...
2.CPU在非常空閒的情況下,打印結果就會是:m start...和m end...
使用volatile修飾的變量不允許線程(核)內部緩存和重排序,直接修改內存,對於其他線程是可見的。
聲明變量是 volatile 的,JVM 保證了每次讀變量都從內存中讀,跳過 CPU cache 這一步。
二、指令重排是什麼呢?
是指CPU採用了允許將多條指令不按程序規定的順序分開發送給各相應電路單元處理。
Test t = new Test()
上面的代碼雖然只有一行,但不是原子操作,它分成3步驟:
1.開闢內存空間
2.初始化對象
3.把初始化的對象指向內存地址上
new 對象Test時3步操作的執行順序是不一定的,有可能是132或123。
如果是132這樣,在13之後程序意外終止,t != null,但是 t 還沒初始化完成
如果加了volatile關鍵字就禁止了指令重排。程序是123這樣執行,如果12或1執行了之後程序意外終止,t還是null。