bilibili-Java併發學習筆記8 volatile
基於 java 1.8.0
P33_volatile關鍵字作用與鎖的關係深入詳解
volatile 的作用:
- 實現
long
/double
類型變量的原子操作- volatile double a = 1.0;
- jdk1.5 AtomicLong
- 防止指令重排序
- 實現變量的可見性
- JMM
- 使用 volatile 時,不再會從寄存器中獲取緩存值,而是直接從內存中獲取;
volatile 與 鎖 相似的特點:
- 確保變量的內存可見性
- 防止指令重排序
volatile 與 鎖 的區別:
- volatile 可以確保對變量
寫操作的原子性
,但不具備排他性(互斥性); - 使用鎖可能會導致線程的上下文切換(內核態與用戶態的切換),但 volatile 並不會出現這種情況;
不當用法:
- volatile int a = b + 2;
- volatile int a = a++;
- volatile Date date = new Date();
適合用法:
- volatile int a = 7;
- volatile boolean flag = false;
如果要實現 volatile 寫操作的原子性,那麼在等號右側的賦值變量中就不能出現被多線程所共享的變量,哪怕這個變量也是 volatile 修飾的也不行;
P34_volatile與內存屏障的重要語義詳細分析
防止指令重排序與實現變量的可見性都是通過一種手段來實現的:內存屏障
(memory barrier);
package new_package.thread.p33;
public class VolatileTest {
int a = 1;
String s = "hello";
// 內存屏障 (Release Barrier,釋放屏障): 防止下面的 volatile 與上面的操作的指令重排序
volatile boolean v = false;//寫入操作
// 內存屏障 (Store Barrier,存儲屏障): 刷新處理器的緩存,可以確保該存儲屏障之前的一切操作所生成的結果對於其他處理器來說都可見了
String s2 = s + "hello";
// ---
// 內存屏障 (Load Barrier,加載屏障): 可以刷新處理器的緩存,同步其他處理器對該 volatile 變量的修改結果
boolean v1 = v; // 讀取操作
// 內存屏障 (Acquire Barrier,獲取屏障): 可以防止之前的 volatile 讀取操作與之後的所有操作語句的指令重排序
String h = "world";
// 對於 volatile 變量的讀寫操作,本質上都是通過內存屏障來實現的。
// 內存屏障兼備了兩方面的能力:1.防止指令重排序,2.實現變量內存的可見性
// 對於讀操作 :volatile 可以確保該操作與之後的所有讀寫操作都不會進行指令重排序;
// 對於寫操作 :volatile 可以確保該操作與之前的所有讀寫操作都不會進行指令重排序;
}
package new_package.thread.p33;
import java.util.Date;
public class SynchronizedTest {
// 鎖同樣具備變量內存可見性與防止指令重排序的功能
synchronized void func() {
// monitorenter
// 內存屏障 (Acquire Barrier,獲取屏障)
// 邏輯代碼
Date date = new Date();
String name = "world";
// 內存屏障 (Release Barrier,釋放屏障)
// monitorexit
}
}
P35_JMM與happen-before規則深入詳解
- 變量的原子性問題
- 變量的可見性問題
- 變量修改的時序性問題
happen-brfore 重要規則:
- 順序執行規則(限定在單個線程上的):該線程的每個動作都 happen-before 之後的動作;
- 隱式鎖(monitor)規則 : unlock 是 happen-before lock ,之前的線程對於同步代碼塊的所有執行結果對於後續獲取鎖的線程來說都是可見的;
- volatile 讀寫規則 : 對於一個 volatile 變量的寫操作一定會 happen-before 後續對該變量的讀操作;
- 多線程的啓動規則 : Thread 對象的 start 方法 happen-before 該線程 run 方法中的任何一個動作,包括在其中啓動的任何子線程;
- 多線程的終止規則 : 一個線程啓動了一個子線程,並且調用了子線程的 join 方法等到其結束,那麼當子線程結束後,父線程的接下來的所有操作都可以看到子線程 run 方法中的執行結果;
- 線程的中斷規則 : 可以調用 interrupt 方法來中斷線程,這個調用 happen-before 對該線程中斷的檢查(isInterrupted);