更新時(shí)間:2023年04月25日09時(shí)33分 來(lái)源:傳智教育 瀏覽次數(shù):
在計(jì)算機(jī)程序中,當(dāng)多個(gè)線程同時(shí)訪問(wèn)同一個(gè)變量時(shí),可能會(huì)發(fā)生線程安全問(wèn)題,其中之一是變量的可見(jiàn)性問(wèn)題。這意味著一個(gè)線程在修改了一個(gè)變量的值之后,其他線程無(wú)法立即感知到這個(gè)變化,導(dǎo)致程序出現(xiàn)不一致的行為。
為了解決這個(gè)問(wèn)題,Java提供了一個(gè)關(guān)鍵字volatile。使用volatile關(guān)鍵字聲明的變量,其特點(diǎn)如下:
1.可見(jiàn)性:對(duì)于一個(gè)volatile變量的寫(xiě)操作,JVM會(huì)立即把修改后的值刷新回主內(nèi)存中,而不是僅僅保留在本地緩存中。這樣,其他線程就能夠看到該變量的最新值,從而避免了可見(jiàn)性問(wèn)題。
2.禁止指令重排:volatile關(guān)鍵字還可以禁止JVM對(duì)指令的重排優(yōu)化。在不加volatile關(guān)鍵字的情況下,JVM為了提高性能,可能會(huì)對(duì)指令進(jìn)行重排,導(dǎo)致程序出現(xiàn)意外的行為。而使用volatile關(guān)鍵字聲明的變量,JVM會(huì)保證指令的執(zhí)行順序和程序的代碼順序一致,從而避免了這種問(wèn)題。
接下來(lái),我們通過(guò)一段代碼來(lái)演示下volatile關(guān)鍵字如何保證可見(jiàn)性:
public class VolatileDemo { private volatile boolean flag = false; public void setFlag() { flag = true; } public void printFlag() { System.out.println("flag = " + flag); } public static void main(String[] args) { final VolatileDemo demo = new VolatileDemo(); new Thread(() -> { try { Thread.sleep(1000); // 模擬線程1執(zhí)行時(shí)間 } catch (InterruptedException e) { e.printStackTrace(); } demo.setFlag(); }).start(); new Thread(() -> { while (!demo.flag) { // 如果flag不是volatile,該循環(huán)可能會(huì)一直執(zhí)行下去 // do nothing } demo.printFlag(); }).start(); } }
在這個(gè)代碼中,我們聲明了一個(gè)名為flag的布爾型volatile變量。在程序啟動(dòng)后,我們啟動(dòng)了兩個(gè)線程,線程1會(huì)在1秒后將flag的值設(shè)為true,線程2會(huì)在flag變?yōu)閠rue之前一直循環(huán)等待。由于flag是volatile類(lèi)型,線程2能夠正確感知到flag的變化,從而在flag變?yōu)閠rue后打印出相應(yīng)的信息。如果flag不是volatile類(lèi)型,線程2可能會(huì)一直等待下去,因?yàn)樗鼰o(wú)法感知到flag的變化,從而導(dǎo)致程序出現(xiàn)錯(cuò)誤的結(jié)果。
需要注意的是,volatile關(guān)鍵字雖然可以保證可見(jiàn)性和禁止指令重排,但并不能保證原子性。也就是說(shuō),如果一個(gè)變量被多個(gè)線程同時(shí)訪問(wèn),并且這些線程都對(duì)它進(jìn)行修改操作,那么使用volatile關(guān)鍵字并不能保證程序的正確性。這時(shí)候需要使用其他的線程同步機(jī)制,如synchronized關(guān)鍵字或者Lock接口等。
北京校區(qū)