volatile是Java虛擬機提供的 輕量級 的同步機制(“乞丐版”的synchronized) 1. 保證可見性 2. 不保證原子性 3. 禁止指令重排 可見性 指當多個線程訪問同一個變數時,如果其中一個線程修改了這個變數的值,其他線程能夠立即看得到修改的值 驗證可見性demo: 結果: 不保證 ...
volatile是Java虛擬機提供的輕量級的同步機制(“乞丐版”的synchronized)
- 保證可見性
- 不保證原子性
- 禁止指令重排
可見性
指當多個線程訪問同一個變數時,如果其中一個線程修改了這個變數的值,其他線程能夠立即看得到修改的值
驗證可見性demo:
import java.util.concurrent.TimeUnit;
class MyData {
volatile int number = 0;
public void addTo60() {
number = 60;
}
}
public class VolatileDemo {
public static void main() {
MyData myData = new MyData();
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t come in");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
myData.addTo60();
System.out.println(Thread.currentThread().getName() + "\t updated number: " + myData.number);
}, "AAA").start();
while (myData.number == 0) {}
System.out.println(Thread.currentThread().getName() + "\t mission is over");
}
}
結果:
AAA come in
main mission is over
AAA updated number: 60
不保證原子性
原子性:程式中的所有操作是不可中斷的,要麼全部執行成功要麼全部執行失敗
不保證原子性正是volatile輕量級的體現,多個線程對volatile修飾的變數進行操作時,會出現容易出現寫覆蓋的情況(i++)
驗證不保證原子性demo:
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
class MyData {
volatile int number = 0;
public void addPlusPlus() {
number++;
}
}
public class VolatileDemo {
public static void main(String[] args) {
MyData myData = new MyData();
for (int i = 0; i < 20; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
myData.addPlusPlus();
}
}, String.valueOf(i)).start();
}
while (Thread.activeCount() > 2) {
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + "\t finally number value: " + myData.number);
}
}
結果:
main finally number value: 19109
解決不保證原子性問題:Atomic
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
class MyData {
volatile int number = 0;
public void addPlusPlus() {
number++;
}
AtomicInteger atomicInteger = new AtomicInteger();
public void addAtmic() {
atomicInteger.getAndIncrement();
}
}
public class VolatileDemo {
public static void main(String[] args) {
MyData myData = new MyData();
for (int i = 0; i < 20; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
myData.addAtmic();
}
}, String.valueOf(i)).start();
}
while (Thread.activeCount() > 2) {
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + "\t finally number value: " + myData.number);
System.out.println(Thread.currentThread().getName() + "\t AtomicInteger type,finally number value: "
+ myData.atomicInteger);
}
}
結果:
main finally number value: 19746
main AtomicInteger type,finally number value: 20000
禁止指令重排
指令重排:為了提高程式運行效率,編譯器可能會對輸入指令進行重新排序,即程式中各個語句的執行先後順序同代碼中的順序不一定一致。(但是它會保證單線程程式最終執行結果和代碼順序執行的結果是一致的,它忽略了數據的依賴性)
源代碼 -> 編譯器優化重排 -> 指令並行重排 -> 記憶體系統重排 -> 最終執行指令
volatile能夠實現禁止指令重排的底層原理:
- 記憶體屏障(Memory Barrier):它是一個CPU指令。由於編譯器和CPU都能夠執行指令重排,如果在指令間插入一條Memory Barrier則會告訴編譯器和CPU,任何指令都不能和該條Memory Barrier指令進行重排序,即通過插入記憶體屏障指令能夠禁止在記憶體屏障前後的指令執行重排序
優化 - 記憶體屏障的另外一個作用是強制刷新各種CPU的緩存數據,因此任何CPU上的線程都能夠讀取到這些數據的最新版本。以上兩點正好對應了volatile關鍵字的禁止指令重排序和記憶體可見性的特點
- 對volatile變數進行寫操作時,會在寫操作之後加入一條store屏障指令,將工作記憶體中的共用變數copy刷新回主記憶體中;對volatile變數進行讀操作時,會在讀操作之前加入一條load的屏障指令,從主記憶體中讀取共用變數
應用場景:
-
高併發環境下DCL單例模式使用volatile
public class SingletonDemo { private static volatile SingletonDemo instance = null; private SingletonDemo() { System.out.println(Thread.currentThread().getName() + "我是構造方法SingletonDemo()"); } public static SingletonDemo getInstance() { if (instance == null) { synchronized (SingletonDemo.class) { if (instance == null) { instance = new SingletonDemo(); } } } return instance; } public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(() -> { SingletonDemo.getInstance(); }, String.valueOf(i)).start(); } } }
-
JUC包下AtomicXxx類:原子類AtomicXxx中都有一個成員變數value,該value變數被聲明為volatile,保證
AtomicXxx類的記憶體可見性,而原子性由CAS演算法&Unsafe類保證,結合這兩點才能讓AtomicXxx類很好地替代synchronized關鍵字。public class AtomicInteger extends Number implements java.io.Serializable { // ... private volatile int value; // ... }