Java 之 volatile 超級詳解

1、概念

volatile 是 Java 中的關鍵字,是一個變量修飾符,被用來修飾會被不一樣線程訪問和修改的變量。緩存

 

2、volatile 做用

1. 可見性安全

可見性是指多個線程訪問同一個變量時,其中一個線程修改了該變量的值,其它線程可以當即看到修改的值。多線程

在 Java 內存模型中,全部的變量都存儲在主存中,同時每一個線程都擁有本身的工做線程,用於提升訪問速度。線程會從主存中拷貝變量值到本身的工做內存中,而後在本身的工做線程中操做變量,而不是直接操做主存中的變量,因爲每一個線程在本身的內存中都有一個變量的拷貝,就會形成變量值不一致的問題。併發

以下面的代碼所示:app

測試類:測試

class VolatileTestObj { private String value = null; private boolean hasNewValue = false; public void put(String value) { while (hasNewValue) { // 等待,防止重複賦值 } this.value = value; hasNewValue = true; } public String get() { while (!hasNewValue) { // 等待,防止獲取到舊值 } String value = this.value; hasNewValue = false; return value; } }

測試代碼:優化

public class VolatileTest { public static void main(String... args) { VolatileTestObj obj = new VolatileTestObj(); new Thread(() -> { while (true) { obj.put("time:" + System.currentTimeMillis()); } }).start(); new Thread(() -> { while (true) { System.out.println(obj.get()); } }).start(); } }

以上測試代碼中,一個線程進行賦值操做,另外一個線程取值,運行該測試代碼能夠發現,很容易阻塞在循環等待中。this

這是由於寫線程寫入一個新值,同時將 hasNewValue 置爲 true,可是隻更新了寫線程本身工做線程的緩存值,沒有更新主存中的值。而讀線程在獲取新值是,其工做線程中的 hasNewValue 爲 false,會陷入到循環等待中,即便寫線程寫了新值,讀線程也沒法獲取。由於讀線程沒有獲取都新值,寫線程的 hasNewValue 沒有被置回 false,因此寫線程也會陷入到循環等待中。所以產生了死鎖。spa

使用 volatile 關鍵字能夠解決這個問題,使用 volatile 修飾的變量確保了線程不會將該變量拷貝到本身的工做線程中,全部線程對該變量的操做都是在主存中進行的,因此 volatile 修飾的變量對全部線程可見。線程

使用 volatile 修飾 hasNewValue,這樣在寫線程和讀線程中都是在主存中操做 hasNewValue 的值,就不會產生死鎖。

2. 原子性

volatile 只保證單次讀/寫操做的原子性,對於多步操做,volatile 不能保證原子性,以下代碼所示:

測試類:

class VolatileCounter { private volatile int count = 0; public void inc() { count++; } public void dec() { count--; } public int get() { return count; } }

測試代碼:

public class VolatileTest { public static void main(String... args) { while (true) { VolatileCounter counter = new VolatileCounter(); Thread thread1 = new Thread(() -> { for (int i = 0; i < 50; i++) { counter.inc(); } }); Thread thread2 = new Thread(() -> { for (int i = 0; i < 50; i++) { counter.dec(); } }); thread1.start(); thread2.start(); try { thread1.join(); thread2.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("counter = " + counter.get()); } } }

運行結果:

... counter = 0 counter = 0 counter = 0 counter = 0 counter = -21 counter = 0 counter = 0 counter = 0 counter = 0 ...

從運行結果能夠看出,絕大部分狀況下輸出結果爲 counter = 0,但也有部分其它結果。由此可知,對於 count++; 和 count--; 這兩個操做並不具備原子性。

這是由於 count++ 是一個複合操做,包括三個部分:

  1. 讀取 count 的值;
  2. 對 count 加 1;
  3. 將 count 的值寫回內存;

volatile 對於這三步操做是沒法保證原子性的,因此會出現上述運行結果。

因此,vloatile 並不能解決全部同步的問題

3. 有序性

在 Java 內存模型中,容許編譯器和處理器對指令進行重排序,重排序過程不會影響到單線程程序的執行,可是會影響到多線程併發執行的正確性。

volatile 關鍵字能夠禁止指令從新排序,能夠保證必定的有序性。

volatile 修飾的變量的有序性有兩層含義:

  1. 全部在 volatile 修飾的變量寫操做以前的寫操做,將會對隨後該 volatile 修飾的變量讀操做以後的語句可見。
  2. 禁止 JVM 重排序:volatile 修飾的變量的讀寫指令不能和其先後的任何指令重排序,其先後的指令可能會被重排序。

3.1 happen-before

happen-before 關係是用來判斷是否存在數據競爭、線程是否安全的主要依據,也是指令重排序的依據,保證了多線程下的可見性。

volatile 修飾的變量在讀寫時會創建 happen-before 關係。

以下面的測試類:

class VolatileOrder { int i = 0; volatile boolean flag = false; public void write() { i = 1; // 步驟 1 flag = true; // 步驟 2 } public String get() { if (flag) { // 步驟 3 System.out.println("i = " + i); // 步驟 4 } } }

上面的代碼依據 happen-before 原則(關於 happen-before 原則可自行搜索)會創建以下的關係:

  • 根據 happen-before 單線程順序原則會有:步驟 1 happen-before 步驟 二、步驟 3 happen-before 步驟 4;
  • 根據 happen-before 的 volatile 原則會有:步驟 2 happen-before 步驟 3;
  • 根據 happen-before 的傳遞性原則會有:步驟 1 happen-before 步驟 4;

因此 步驟 1 對於 步驟 4 是可見的,即變量 i 在多個線程中具備可見性。

這也解釋了 volatile 有序性的第一層含義:全部在 volatile 修飾的變量寫操做以前的寫操做,將會對隨後該 volatile 修飾的變量讀操做以後的語句可見。

利用這個特性能夠優化變量在線程間的可見性,不須要對每一個變量都用 volatile 修飾,只須要用 volatile 修飾一部分變量便可保證其它變量在多線程間也具備可見性。

3.2 禁止 JVM 重排序

對於上述代碼,若是變量 flag 沒有使用 volatile 修飾,那麼步驟 1 和步驟 2 就有可能被 JVM 重排序,就沒法獲得上述的 happen-before 關係,因此 volatile 修飾的變量禁止 JVM 重排序。

以下代碼所示:

class VolatileOrder { int a, b, c; volatile int d; void write() { a = 1; b = 2; c = 3; d = 4; } void read() { int D = d; int A = a; int B = b; int C = c; } }

在 write() 方法中:

a = 1; b = 2; c = 3;

JVM 可能會重排序這三個指令,可是這三個指令必定是排在 d = 4; 這個指令以前。

一樣的,在 read() 方法中:

int A = a; int B = b; int C = c;

JVM 可能會重排序這三個指令,可是這三個指令必定是排在 int D = d; 這個指令以後。

相關文章
相關標籤/搜索