Java多線程——volatile

volatile關鍵字介紹

volatile修飾的變量在多處理器開發中保證了共享變量的“可見性”。可見性的意思是當一個線程修改一個共享變量時,另外一個線程能讀到這個修改的值。Java中的volatile關鍵字用作Java編譯器和Thread的指示符,它們不會緩存此變量的值並始終從主存中讀取它。

Java在Java內存模型(JMM)中引入了一些變化,它保證了從一個線程到另一個線程的變化的可見性,也就是“happens-before”在一個線程中發生的內存寫入的問題可能“泄漏”並被另一個線程看到。

這裏寫圖片描述

圖片來自: http://javarevisited.blogspot.jp/2011/06/volatile-keyword-java-example-tutorial.html

volatile使用要點

  1. Java中的volatile關鍵字保證volatile變量的值總是從主存儲器讀取而不是從線程的本地緩存讀取。

  2. 在Java中,對於使用Java volatile關鍵字(包括long和double變量)聲明的所有變量,讀寫操作都是原子性的。

  3. 在變量中使用Java中的volatile關鍵字可以減少內存一致性錯誤的風險,因爲對Java中volatile變量的任何寫入與該變量的後續讀取建立了一個happens-before關係。

  4. 對於大多數基本變量(除long和double之外的所有類型),即使沒有在Java中使用volatile關鍵字,對於引用變量的讀和寫是原子的。

  5. 對Java中volatile變量的訪問從來沒有機會阻塞,因爲我們只做一個簡單的讀或寫,因此與synchronized塊不同,我們永遠不會持有任何鎖或等待任何鎖。

  6. 作爲對象引用的Java volatile變量可以爲null。

  7. Java volatile關鍵字並不意味着原子,比如對聲明volatile變量 i++ 操作並不是原子的,使操作原子你仍然需要確保使用synchronized方法或在Java中的塊進行獨佔訪問。

  8. 如果變量不在多個線程之間共享,則不需要對該變量使用volatile關鍵字。

與synchronized的區別

  1. Java中的volatile關鍵字是一個字段修飾符,而同步修改代碼塊和方法。

  2. synchronized需要獲取和釋放監視器鎖,而 volatile關鍵字不需要持有鎖。

  3. 在Java中的線程可以被阻塞以等待任何監視器在同步的情況下,而Java中的volatile關鍵字不是這樣。

  4. 同步方法比Java中的volatile關鍵字影響性能。

  5. 由於Java中的volatile關鍵字僅同步線程內存和“主”內存之間的一個變量的值,而同步則同步線程內存和“主”內存之間的所有變量的值,並鎖定和釋放監視器以進行引導。由於這個原因,Java中的synchronized關鍵字很可能比volatile具有更多的開銷。

  6. 不能在空對象上同步,但Java中的volatile變量可以爲null。

示例代碼

這裏舉例說明volatile修飾變量的複合操作 i++ 不具有原子性。

public class VolatilePractice {

    private volatile int i = 0;

    public int get(){
        return i;  ////單個volatile變量的讀與寫具有原子性
    }

    public void set(int n){
        this.i = n;
    }
    //如果在方法上加synchronized修飾
    public void getAndIncrement(){
        i++;  //複合(多個)volatile變量的讀/寫不具有原子性
    }

    public static void main(String[] args){

        VolatilePractice volatilePractice = new VolatilePractice();

        ExecutorService executorService = Executors.newFixedThreadPool(30);
        for(int i = 0; i < 10000; i++){
            executorService.execute(new VolatileAtomicity(volatilePractice));
        }

        executorService.shutdown();
        //判斷是否所有線程執行完成
        while(executorService.isTerminated()){

            System.out.println(volatilePractice.get());
            break;
        }

    }
}

class VolatileAtomicity implements Runnable{

    private VolatilePractice volatilePractice;

    public VolatileAtomicity(VolatilePractice volatilePractice){
        this.volatilePractice = volatilePractice;
    }

    public void run(){
        volatilePractice.getAndIncrement();
    }
}

如果 i++ 操作是原子的,正常情況下打印的結果應該是10000,但實際每次的結果大都不同並且小於10000; 如果在 getAndIncrement() 方法上加 synchronized 關鍵字(或者方法內用 lock 等),就能保證該方法操作的原子性了,就會得到輸出值 10000 了。

參考資料

How Volatile in Java works? Example of volatile keyword in Java
Java併發編程:volatile關鍵字解析

發佈了218 篇原創文章 · 獲贊 267 · 訪問量 67萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章