Volatile 保證可見性 private volatile static Integer num = 0; 使用了volatile關鍵字,即可保證它本身可被其他線程的工作記憶體感知,即變化時也會被同步變化。 不保證原子性 原子性:不可分割 線程A在執行任務時是不可被打擾的,也不能被分割,要麼同時成 ...
Volatile
保證可見性
private volatile static Integer num = 0;
使用了volatile關鍵字,即可保證它本身可被其他線程的工作記憶體感知,即變化時也會被同步變化。
不保證原子性
原子性:不可分割
線程A在執行任務時是不可被打擾的,也不能被分割,要麼同時成功,要麼同時失敗。
package org.example.tvolatile;
public class VDemo02 {
//synchronized保證原子性,每次只有一條線程執行,所以結果準確
//volatile不保證原子性,雖然也是同步機制,但是結果不准確
private volatile static int num = 0;
public static void add(){
num++;
}
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
while (Thread.activeCount()>2){//主線程和GC線程
Thread.yield();//讓主線程禮讓
}
System.out.println(num);
}
}
每次結果也不一樣。
如果不加Lock加synchronized,怎麼保證原子性?
要瞭解為什麼一個num++都不能保證原子性,首先我們需要查看到他編譯好的class位元組碼文件,找到target,並且右鍵選擇打開外部的文件,找到對應的class文件,再通過javap -c命令反編譯查看位元組碼文件。
查看到位元組碼文件後可以看到從底層看,其實num++這個操作再多線程下並不安全,有獲取和寫回這兩個操作
那麼又回到了這個問題,在volatile中如何保證原子性。打開jdk幫助文檔,我們能找到原子性的一些數據類型
在volatile需要保證原子性操作的時候使用原子類來解決原子問題。
原子類為什麼高級
原子類的包裝類底層使用的是CAS操作。
package org.example.tvolatile;
import java.util.concurrent.atomic.AtomicInteger;
public class VDemo02 {
//synchronized保證原子性,每次只有一條線程執行,所以結果準確
//volatile不保證原子性,雖然也是同步機制,但是結果不准確
private volatile static AtomicInteger num = new AtomicInteger(0);
public static void add(){
num.getAndIncrement();//AtomicInteger+1的方法,並不是簡單的+1操作,方法:CAS
}
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
while (Thread.activeCount()>2){//主線程和GC線程
Thread.yield();//讓主線程禮讓
}
System.out.println(num);
}
}
這些類的底層都直接調用c++和操作系統掛鉤!在記憶體中修改值。Unsafe類是一個很特殊的存在!Unsafe類的所有方法都是native方法,調用c++底層
native是與C++聯合開發的時候用的!java自己開發不用的!
禁止指令重排
什麼是指令重排:你寫的程式並不是按照你寫的那樣去執行的
源代碼-----編譯器優化的指令重排--------指令並行也可能會指令重排--------記憶體系統也會重排--------->執行
int x = 1;//1
int y = 2;//2
x = x+5;//3
y = x*x;//4
我們所期望的執行順序:1、2、3、4;但是如果按照1、3、2、4或者2、4、1、3的順序執行也是能夠順利運行的,電腦執行時可能對對我們所期望執行順序的程式進行指令重排,結果是正確的但是對運行順序有所不同
但是指令重排後的順序不可能是4、3、2、1或者其它不符合邏輯的順序。因為處理器在進行指令重拍的過程中會考慮數據之間的依賴性
可能造成影響的結果:四個初始值都為零
線程A | 線程B |
---|---|
x = a; | y = b; |
b = 1; | a = 2; |
由於兩個線程的操作都沒有數據依賴性,指令重排就不會考慮順序問題,可能會導致最終的執行順序如下
線程A | 線程B |
---|---|
b = 1; | a = 2; |
x = a; | y = b; |
多線程下可能導致一些問題
正常結果:x=0;y=0;
指令重排導致的詭異結果:x=2;y=1;
這是一種概念可能你寫1000w行代碼都不一定會出現,但是在理論上是一定會參數的。
volatile可以避免指令重排
電腦中存在著一種指令叫做記憶體屏障,它是一種CPU指令。
作用:
1、保證特定操作的執行順序(可以讓volatile避免指令重排)
2、可以保證某些變數的記憶體可見性(可以讓volatile實現可見性)
Volatile保證可見性、不能保證原子性、由於記憶體屏障,可以避免指令重排的現象產生!
記憶體屏障在單例模式中使用的最多!