簡單學:併發程式設計之volatile

LemonYang發表於2016-12-09

關於java併發程式設計的相關文章都是閱讀了《java併發程式設計實戰》之後的讀書筆記總結,另外本文還參考和引用了Java 理論與實踐: 正確使用 Volatile 變數

在java的鎖機制中(例如synchronized),主要包含了兩種特性,即原子性(互斥)和可見性。原子性即一次只允許一個執行緒能夠持有某個特定的鎖,並訪問其程式碼塊。因此原子性可以用於實現對共享資料對協調訪問,一次只有一個執行緒可以訪問其共享物件。

volatile保證可見性

java中的volatile關鍵字可以被認為是一種輕量級的synchronized機制,訪問volatile變數的時候並不會執行加鎖操作,不會導致執行緒阻塞,同時它又和鎖機制一樣,都具備可見性的特性(但是並不具備原子特性)。當把變數生命為volatile型別後,編譯器與執行時都會注意到這個變數是共享的,因此不會把這個變數上的操作與其他記憶體操作一起重排序。volatile變數不會被快取在暫存器或者其他處理器不可見的地方。因此在讀取volatile變數的時候總是會返回最新寫入的值。

volatile提供執行緒安全

volatile變數可以用於提供執行緒安全,但是必須同時滿足以下兩個條件:

  • 對變數的寫操作不依賴於當前的值

  • 該變數沒有包含在具有其他變數的不變式之中

正確使用volatile關鍵字

在使用volatile關鍵字的時候,要始終牢記一個原則--只有在狀態真正獨立於程式內其他內容時才能使用 volatile

  • 狀態標識:

    volatile變數可以作為一個布林狀態標識,用於指示發生一次重要性時間,例如停止執行緒等

class Test {
    static volatile boolean isRunning;

    private static class TestThread extends Thread {
        public void run() {
            while (!isRunning) {
                System.out.println("the thread is still running");
            }
        }
    }

    public static void main(String[] args) {
        new TestThread().start();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        isRunning = true;
    }
}複製程式碼

在上面的例子中,我們在isRunning這個布林變數前面加上了volatile進行修飾,以確保在main執行緒當中對它的修改能夠被我們開啟的子執行緒所看到。(如果我們沒有新增volatile關鍵字,有可能子執行緒會持續執行而看不到isRunning的值的改變)

  • 一次性安全釋出:

    在缺乏同步的情況下,可能會遇到某個物件引用的更新值(由另一個執行緒寫入)和該物件狀態的舊值同時存在。通過使用volatile關鍵字,我們可以實現安全釋出物件。

public class BackgroundFloobleLoader {
    public volatile Flooble theFlooble;

    public void initInBackground() {
        theFlooble = new Flooble();  // this is the only write to theFlooble
    }
}

public class SomeOtherClass {
    public void doWork() {
        while (true) {
            // use the Flooble, but only if it is ready
            if (floobleLoader.theFlooble != null)
                doSomething(floobleLoader.theFlooble);
        }
    }
}複製程式碼

⚠️上面的安全釋出的必要條件是:被髮布的物件必須是執行緒安全的,或者是有效的不可變物件(有效不可變意味著物件的狀態在釋出之後永遠不會被修改)。volatile 型別的引用可以確保物件的釋出形式的可見性,但是如果物件的狀態在釋出後將發生更改,那麼就需要額外的同步。

  • 獨立觀察:

    定期釋出觀察結果供程式內部使用。如下面的例子所示:

public class UserManager {
    public volatile String lastUser;

    public boolean authenticate(String user, String password) {
        boolean valid = passwordIsValid(user, password);
        if (valid) {
            User u = new User();
            activeUsers.add(u);
            lastUser = user;
        }
        return valid;
    }
}複製程式碼

⚠️上面這種用例要求被髮布的值是有效不可變的 —— 即值的狀態在釋出後不會更改

  • 開銷較低的讀-寫鎖策略:

    如果對於某個變數的值的讀操作遠遠超過寫操作,我們可以通過將內建鎖(幫助我們實現操作的原子性)和volatile關鍵字相結合,實現開銷較低的讀-寫鎖.

@ThreadSafe
public class CheesyCounter {
    // Employs the cheap read-write lock trick
    // All mutative operations MUST be done with the 'this' lock held
    @GuardedBy("this");
    private volatile int value;

    public int getValue() {
        return value;

    public synchronized int increment() {
        return value++;
    }
}複製程式碼

相關文章