Java多執行緒——volatile

gary-liu發表於2017-03-18

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關鍵字解析

相關文章