Java并發編程 - volatile關鍵解析

volatile關鍵字可以說是Java虛擬機提供的最輕量級的同步機制,但是它并不容易彎曲被正確、完整的理解。

volatile的特性

volatile關鍵字是Java虛擬機提供的最輕量級的同步機制。Java內存模型對volatile專門定義了一些特殊的訪問規則。

當一個變量被volatile修飾后,它將具備兩種特性:

1. 可見性

** 第一是保證此變量對所以線程的可見性** ,這里的"可見性"是指當一個線程修改了這個變量的值,新值對于其它線程來說是可以立即得知的。而普通變量不能做到這一點,普通變量的值在線程間傳遞均需要通過主內存來完成,例如,線程A修改一個普通的變量的值,然后向主內存進行回寫,另外一條線程B在線程A回寫完成了之后再從主內存進行讀取操作,新變量值才會對線程B可見。

關于volatile變量的可見性,經常會被開發人員誤解,認為以下描述成立:"volatile變量對所有線程是立即可見,對volatile變量所有的寫操作都能立刻反映到其他線程之中,換句話說,volatile變量在各個線程中是一致的,所以基于volatile變量的運算在并發情況下是安全的"。這句話論據部分并沒有錯,但是其論據并不能得出"基于volatile變量的運算在并發情況下是安全的"這個結論。volatile變量在各個線程的工作內存中不存在一致性問題(在各個線程的工作內存中,volatile變量也可以存在不一致的情況,但由于每次使用之前都要先刷新,執行引擎看不到不一致的情況,因此可以認為不存在一致性問題),但是Java里面的運算并非是原子操作,導致volatile變量的運算在并發情況下一樣是不安全的。

例如:

package com.bytebeats.codelab;

import java.util.concurrent.CountDownLatch;

/**
 * ${DESCRIPTION}
 *
 * @author Ricky Fung
 */
public class VolatileDemo {

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

        VolatileDemo demo = new VolatileDemo();
        demo.run();
    }

    public void run() throws InterruptedException {

        int threadNum = 20;
        final CountDownLatch latch = new CountDownLatch(threadNum);
        for (int i=0; i<threadNum; i++){

            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {

                    for(int x=0; x<50; x++){
                        incr();
                    }
                    latch.countDown();
                }
            });
            t.start();
        }

        //等待所有線程執行完累加操作
        latch.await();

        System.out.println(race);
    }

    private volatile int race = 0;

    private void incr(){
        race++;
    }
}

模擬了20個線程對race變量進行自增操作,如果這段代碼是并發安全的話,最后輸出的結果應該為1000。但事實上每次運行上述代碼,輸出的結果可能都會不一樣,都是一個小于1000的數。

究其原因,就是Java 中 race++不是原子操作。

由于volatile變量,只能保證可見性,在不符合以下兩條規則的情況下,我們仍然需要通過加鎖(使用synchronized 或 java.util.concurrent中的原子類)來保證原子性。

  • 運算結果并不依賴變量的當前值,或者能夠確保只有單一的線程修改變量的值。
  • 變量不需要與其他的狀態變量共同參與不變約束。

2. 禁止指令重排序優化

使用volatile變量的第二個語義是禁止指令重排序優化,

原子性、可見性、有序性

Java內存模型是圍繞著在并發過程中如何處理 原子性、可見性和有序性 這3個特征來建立的。我們來逐個看一下哪些操作實現了這3個特性。

原子性

由Java內存模型來直接保證的原子性變量操作包括:read、load、assign、use、store和write,我們大致可以認為基本數據類型的訪問操作是具備原子性的(例外的是long和double的非原子性協定)。

如果硬要程序需要一個更大范圍的原子性保證(經常會遇到),Java內存模型還提供了synchronized 關鍵字和Lock,在synchronized 塊之間的操作也具備原子性。

可見性

可見性是指當一個線程修改了這個變量的值,新值對于其它線程來說是可以立即得知這個修改。

Java內存模型是通過在變量修改后將新值同步回主內存,在變量讀取前從主內存刷新變量值 這種依賴主內存作為傳遞媒介的方式來實現可見性的,無論是普通變量還是volatile變量都是如此,普通變量與volatile變量的區別是:volatile的特殊規則保證了新值能立即同步到主內存,以及每次使用前立即從主內存刷新。因此,可以說volatile保證了多線程操作時 變量的可見性,而普通變量則不能保證這一點。

除了volatile之外,Java還有兩個關鍵字能實現可見性,即synchronized和final。

有序性

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容