爲了防止程序重排序,慎用volatile

    之前在InfoQ看到一篇關於java重排序的一篇文章,覺得裏面有些知識寫得太絕對了,於是想通過實際程序來說明一下:

    關於java重排序,這裏就不做介紹了,我們知道JVM底層封裝了與OS的交互,它內部有自己的一套類似於OS的內存模型,程序重排序的設計思路基本上是來源於OS跟硬件層面的設計。下面直接入正題吧!


    我們知道JVM給每個線程分配了自己的內存空間,也就是說在變量存儲方面,分爲主內存和線程工作內存,也就是說,所有線程共享主內存,每個線程都有自己的工作內存。程序執行的時候是去工作內存裏面取值還是去主內存裏面取值呢?下面以代碼爲例:

public class DemoWork {
    
    private boolean stop=false;
    private boolean start=true;
    
    public void workThread() throws InterruptedException{
        Thread workThread=new Thread(new Runnable() {
            private int i=0;
            @Override
            public void run() {
                // TODO Auto-generated method stub
                while(!stop){
                    i++;
                    /*try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }*/
                }
                start=false;
            }
        });
        workThread.start();
        Thread.sleep(1000);
        stop=true;
        
        Thread printThread=new Thread(new Runnable() {
            private int i=0;
            @Override
            public void run() {
                // TODO Auto-generated method stub
                while(stop&&start){
                 System.out.println("stop is:"+stop);
                 try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }
        });
        printThread.start();
        

    }

    /**
     * @param args
     * @throws InterruptedException 
     */
    public static void main(String[] args) throws InterruptedException {
        // TODO Auto-generated method stub
        DemoWork dw=new DemoWork();
        dw.workThread();
    }

}

上面的代碼是不會停下來的,但是如果把sleep那段代碼的註釋去掉程序就能停下來了,這是什麼原因呢?我的理解是:因爲線程printThread是能正常執行的,所以有兩種可能:

  • 線程workThread裏面工作線程stop變量值沒有收到主存的同步,而它一直取的是自己工作線程裏面的stop值

  • 主線程更新stop沒有更新主內存,以至於主內存裏面保存的stop值一直是false



  以上第二點我覺得是可以排除的,因爲線程printThread裏面的值stop值是true,所以造成以上情況第一點的可能性大一點,那爲什麼把workThread裏面的睡眠去掉之後程序又能正常退出呢?那就應該是在執行這些語句的時候主內存更新了工作內存的緣故了(執行打印語句也會推出,至於這裏面的原因是什麼,暫時還沒看到相關的資料,可能跟JVM的重排序規則有關係,但是規則到底是怎樣的呢?),接下來我們來說說volatile

volatile

  1. (適用於Java所有版本)讀和寫一個volatile變量有全局的排序。也就是說每個線程訪問一個volatile作用域時會在繼續執行之前讀取它的當前值,而不是(可能)使用一個緩存的值。(但是並不保證經常讀寫volatile作用域時讀和寫的相對順序,也就是說通常這並不是有用的線程構建)。

  2. (適用於Java5及其之後的版本)volatile的讀和寫建立了一個happens-before關係,類似於申請和釋放一個互斥鎖[7]。

也就是說在上面workThread線程sleep代碼段註釋的情況下,我們可以使用volatile來修飾stop變量,這樣的話就能強制workThread線程去主內存裏面取stop的值了,但是這樣做的話在高並發現會造成性能問題。之前看了很多的開源代碼,裏面解決以上主內存與工作內存不同步的方式基本上是採用volatile修飾變量解決的。我在想,既然volatile在併發情況下會造成性能問題,在workThread循環快裏面執行什麼類型的代碼快能方便JVM更好的同步主內存跟工作內存的值,那樣的話,在高併發下,就能更快的提高程序性能了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章