public class VisibilityDemo {
private boolean flag = true;
public static void main(String[] args) throws InterruptedException {
VisibilityDemo demo = new VisibilityDemo();
System.out.println("開(kāi)始運(yùn)行");
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
int i = 0;
while (demo.flag){
i++;
}
System.out.println(i);
}
});
thread1.start();
TimeUnit.SECONDS.sleep(2);
demo.flag = false;
System.out.println("運(yùn)行結(jié)束");
}
}
如上扇售,按照這段代碼的目的來(lái)說(shuō),會(huì)在兩秒后打印出 i 的值并結(jié)束莫其。但運(yùn)行后并不會(huì)結(jié)束,如下圖
這就是一個(gè)可見(jiàn)性問(wèn)題
那么問(wèn)題就來(lái)了
- 這段程序?yàn)槭裁床粫?huì)正常運(yùn)行
- 如何能夠讓它正常運(yùn)行
為什么不會(huì)正常運(yùn)行
緩存導(dǎo)致的可見(jiàn)性問(wèn)題
每一個(gè)線(xiàn)程都有自己的本地變量,從內(nèi)存中拷貝到CPU緩存,每個(gè)CPU核心有自己的緩存。但是CPU自身保證了緩存的一致性,緩存刷新會(huì)有延遲沪么,但也是延遲一段時(shí)間 i 就會(huì)打印出來(lái),不會(huì)一直不打印锌半。JVM 的指令重排序
JVM 的運(yùn)行模式分為三種:編譯模式禽车、解釋模式、混合模式
編譯模式:jit 將字節(jié)碼提前編譯為匯編
解釋模式:在程序運(yùn)行時(shí)刊殉,一行一行的將字節(jié)碼編譯為匯編
混合模式:運(yùn)行過(guò)程中殉摔,對(duì)熱點(diǎn)代碼進(jìn)行編譯優(yōu)化,與編譯模式相同记焊,其他非熱點(diǎn)與解釋模式相同
在這個(gè)例子中逸月,由于 jit 的優(yōu)化導(dǎo)致程序沒(méi)有按照預(yù)想的運(yùn)行,優(yōu)化后的程序相當(dāng)于以下偽代碼:
public void run() {
int i = 0;
boolean flag = demo.flag;
while (flag){
i++;
}
System.out.println(i);
}
指令重排序是在匯編階段的操作遍膜,無(wú)法通過(guò)代碼直接看到碗硬,不過(guò)我們可以在 jvm 的啟動(dòng)參數(shù)上加上
-Djava.compiler=NONE
關(guān)閉自動(dòng)優(yōu)化來(lái)驗(yàn)證
可以看到關(guān)閉后就打印出了 i 的值
如何修改代碼使其正常運(yùn)行
剛剛通過(guò)關(guān)閉 jit 的優(yōu)化使其正常運(yùn)行了瓤湘,還可以通過(guò)修改代碼讓它正常運(yùn)行
- volatile
private volatile boolean flag = true;
jvm 規(guī)范規(guī)定了,被 volatile 關(guān)鍵字修飾的變量不能夠進(jìn)行指令重排序恩尾,也不能被CPU緩存弛说,修改的值實(shí)時(shí)地寫(xiě)入內(nèi)存。在這里 volatile 可以解決這個(gè)問(wèn)題特笋。
注意:volatile 是語(yǔ)法層面的剃浇,并不是 volatile 直接起作用巾兆,而是 jvm 規(guī)范規(guī)定了猎物,然后廠(chǎng)商按照規(guī)范開(kāi)發(fā)虛擬機(jī),是在虛擬機(jī)層面實(shí)現(xiàn)了 volatile 涉及的功能角塑。
- synchronized
synchronized 關(guān)鍵字在這里不能解決這個(gè)問(wèn)題蔫磨,鎖可以確保同一時(shí)間只有一個(gè)線(xiàn)程操作 i 變量,但這里不是多線(xiàn)程導(dǎo)致的圃伶,所以不能解決這個(gè)問(wèn)題堤如。
- lock
同 synchronized 也不能解決這個(gè)問(wèn)題。