java多線程之volatile

       volatile是Java提供的一種輕量級的同步機制,它能夠保證變量在多線程環境下的可見性(所謂可見性,是指當一條線程修改了共享變量的值,新值對於其餘線程來講是能夠當即得知的)java

       爲了更好說明可見性,咱們直接寫個例子來測試一下:程序員

public class VolatileTest {

    //public volatile static int num = 0 ;
    public static int num = 0 ;

    public static void main(String[] args) throws InterruptedException {

        final CountDownLatch cdl = new CountDownLatch(1) ;

        new Thread(new Runnable() {
            public void run() {
                long time = System.currentTimeMillis() ;
                while (num==0){

                }
                System.out.println("結束:"+(System.currentTimeMillis()-time));
                cdl.countDown();
            }
        }).start();
        System.out.println("開始運行.......");
        Thread.sleep(1000);
        num++ ;
        System.out.println("修改變量num=="+num);
        cdl.await();
        System.out.println("end");


    }

}

結果:緩存

按正常理解應該1秒後會打印end,可是實際狀況是主線程一直在等待。爲何出現這種狀況呢,咱們須要先了解一下JMM(java內存模型)多線程

 

以Java內存模型爲例,Java內存模型分爲主內存(main memory)和工做內存(work memory)。主內存內的變量由全部線程共享,每一個線程擁有本身的工做內存,裏面的變量包含了線程局部變量。主內存中的變量若是被線程使用到,則線程的工做內存會維護一份主內存變量的副本拷貝。測試

 

線程對變量的全部讀寫操做必須在工做內存中進行,不能直接操做主內存中的變量。不一樣線程之間也沒法直接訪問對方的工做內存。線程間變量的傳遞需經過主內存來完成。線程、主內存、工做內存三者之間的交互關係以下圖:spa

 

若是線程在本身的執行代碼裏修改了定義在主線程(主內存)中的變量,修改直接發生在線程的工做內存裏,而後在某個時刻(Java程序員沒法控制這個時刻,而是由JVM調度的),這個修改從工做內存寫回到主內存。線程

所以咱們可知,雖然咱們在主線程中修改了變量可是子線程由於讀的是本身工做內存的副本因此num仍是爲0,致使一直死循環。3d

上面的例子只要咱們把num使用volatile修飾便可保存變量在子線程中可見blog

public volatile static int num = 0 ;

所以咱們能夠得出一個結論:內存

volatile具有兩種特性,第一就是保證共享變量對全部線程的可見性。將一個共享變量聲明爲volatile後,會有如下效應:

1.當寫一個volatile變量時,JMM會把該線程對應的本地內存中的變量強制刷新到主內存中去;

2.這個寫會操做會致使其餘線程中的緩存無效。

*volatile只能保存變量的可見性,並不能保證原子性。所以並不能徹底替代synchronized,這個在開發的時候須要特別注意。主要緣由是變量的賦值過程主要包含如下3步

1.讀取

2.加一

3.賦值

因此,在多線程環境下,有可能線程A將變量讀取到本地內存中,此時其餘線程可能已經將變量改變,線程A依然對過時的變量進行自加,從新寫到主存中,最終致使告終果不合預期

最後發現個疑問:

public class VolatileTest {

    //public volatile static int num = 0 ;
    public static int num = 0 ;

    public static void main(String[] args) throws InterruptedException {

        final CountDownLatch cdl = new CountDownLatch(1) ;

        new Thread(new Runnable() {
            public void run() {
                long time = System.currentTimeMillis() ;
                while (num==0){
                    System.out.println("11111");
                }
                System.out.println("1結束:"+(System.currentTimeMillis()-time));
                cdl.countDown();
            }
        }).start();

        Thread.sleep(1000);
        num++ ;
        cdl.await();
        System.out.println("end");


    }

}

結果:

若是在循環裏面System.out.println 一下即便變量num沒有被volatile修飾也能夠退出循環,目前懷疑子線程中只要調用主線的方法均會從新同步變量。但願各們大神們能有更專業的解答

相關文章
相關標籤/搜索