談談對volatile的理解
volatile是Java虛擬機提供的輕量級同步機制
- 保證可見性
- 不保證原子性
- 禁止指令重排
- 保證可見性
public class JMMDemo {
// 不加 volatile 程序就會死循環!
// 加 volatile 可以保證可見性
private volatile static int num = 0;
public static void main(String[] args) { // main
new Thread(()->{ // 線程 1 對主內存的變化不知道的
while (num==0){
}
}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
num = 1;
System.out.println(num);
}
}
- 不保證原子性
原子性我們在學數據庫的時候有講過,就是不可分割,在這裏是指一個線程在執行任務的時候,是不能被打擾的,也不能被分割,要麼同時成功,要麼同時失敗。
// volatile 不保證原子性
public class VDemo02 {
// volatile 不保證原子性
// private volatile static int num=0;//用這行代碼的話輸出結果不是2萬
// 我們知道加lock和synchronized可以保證原子性
//如果不通過加鎖的話,來使volatile保證原子性,我們可以通過原子類的 Integer
private volatile static AtomicInteger num = new AtomicInteger();
public static void add(){
// num++; // 不是一個原子性操作
num.getAndIncrement(); // AtomicInteger + 1 方法, CAS
}
public static void main(String[] args) {
//理論上num結果應該爲 2 萬
for (int i = 1; i <= 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000 ; j++) {
add();
}
}).start();
}
while (Thread.activeCount()>2){ // main gc
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + " " + num);
}
}
通過原子類 可以來解決volatile的不保證原子性問題,原子類的底層都是直接和操作系統掛鉤,在內存中直接修改值。
- 禁止指令重排
我們寫的代碼背後都是一個指令一個指令去執行的。
代碼的執行流程如下:
源代碼–>編譯器喲花的重排–>指令並行也可能重排–>內存系統也會重排–>執行
處理器在進行指令重排的時候,考慮:數據之間的依賴性
int x=1;//1
int y=2;//2
x=x+y;//3
y=y*y;//4
我們期望的結果是1234 但是可能執行的時候就變成2134 1324
可能造成的影響的結果:abxy這四個默認值都是0;
線程A | 線程B |
---|---|
x=a | y=b |
b=1 | a=2 |
正常的結果:x=0;y=0;但是看由於指令重排
線程A | 線程B |
---|---|
b=1 | a=2 |
x=a | y=b |
指令重排導致的詭異結果:x=2;y=1;
通俗講volatile可以避免指令重排
內存屏障,CPU指令,作用:
- 保證特定的執行順序
- 可以保證某些變量的內存可見性(利用這些特性實現了可見性)
本文爲學習狂神說的JUC課程的筆記,課程地址:https://www.bilibili.com/video/BV1B7411L7tE?p=32