特征
被volatile修飾的變量,具有兩個特征
- 保證可見性
- 不保證原子性
- 禁止指令重排序
關(guān)于內(nèi)存可見性、原子性、有序性,先來了解一下內(nèi)存模型吧~
java內(nèi)存模型(JMM)
- JMM定義了線程和主內(nèi)存之間的抽相關(guān)
- 每個線程都會有一個私有的本地內(nèi)存,存儲了共享變量的副本
- 共享變量存儲再主內(nèi)存中
- image
特性
- 原子性
- 一個操作要么全部執(zhí)行并且執(zhí)行的過程不會被打斷,要么就不執(zhí)行(有點像事務)
- 下面舉個例子
i = 0; //是原子操作
j = i ; //不是! 包含兩個操作 1.讀取i 2.賦值給j
i++; //不是!三個操作 1.讀取i 2.+1 3.賦值給i
volatile是無法保證復合操作的原子性。想在多線程環(huán)境下保證原子性,可以通過鎖、synchronized來確保
- 可見性
- 多線程訪問一個變量時,一個線程修改變量的值,其他線程能立即看到。
- 但是,多線程環(huán)境下,一個線程修改變量對其他線程是不可見的!
volatile可以保證可見性。當一個變量被volatile修飾之后,該變量被修改后立即更新到內(nèi)存中,讀取的時候會直接從內(nèi)存中讀取。
- 有序性
- 執(zhí)行的順序按照代碼的先后順序執(zhí)行
- 在java內(nèi)存模型中,為了效率,是允許處理器對指令進行重排序的
volatile禁止指令重排序,來保證一定的有序性
指令重排序:是JVM為了優(yōu)化指令,提高程序運行效率,在不影響單線程程序執(zhí)行結(jié)果的前提下,盡可能地提高并行度。注意是單線程,多線程情況下會有問題啊
原理
在jvm底層 是采用‘內(nèi)存屏障’來實現(xiàn)的
-
內(nèi)存屏障 (Memory Barrier)
- 又叫內(nèi)存柵欄,是一個cpu指令
- 插入一條MB,會告訴編譯器和cpu,什么指令都不能和這條MB指令重排序
- MB會強制刷出各種CPU cache,如一個Write-Barrier將刷出所有再Barrier之前寫入cache的數(shù)據(jù),因此cpu上的線程都能讀取到這些數(shù)據(jù)的最新版本
-
****如果一個變量是volatile修飾的,JMM會再寫入這個字段之后插入Write-Barrier指令,在讀這個字段之前插入Read-Barrier指令****,意味著:
- 一個線程寫入變量A后,任何線程都可以拿到最新值
-
happens-before
- 兩個操作間具有h-b關(guān)系,并不以為著前一個操作必須要在后一個操作之前執(zhí)行。
- 僅僅要求前一個操作的執(zhí)行結(jié)果,對后一個操作可見。且前一個操作按順序排在后一個操作之前。
應用場景
- 狀態(tài)量標記
int a = 0;
//修改后立刻對線程可見 比sync lock有一定的效率提升
volatile bool flag = false;
public void write() {
a = 2; //1
flag = true; //2
}
public void multiply() {
if (flag) { //3
int ret = a * a;//4
}
}
- 單例模式的實現(xiàn) 雙重檢查鎖定(DCL)
懶漢模式
class Singleton{
//為了避免初始化操作的指令重排序
private volatile static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if(instance==null) { //B
synchronized (Singleton.class) {
if(instance==null)
//在Singleton構(gòu)造函數(shù)體執(zhí)行之前,變量instance可能成為非null!
instance = new Singleton(); //A
}
}
return instance;
}
}
1.線程1進入到//A處,但在構(gòu)造函數(shù)執(zhí)行之前。使實例成為非null
2.線程2進入//B處,實例不為null,將instance引用返回。返回了一個構(gòu)造完整但部分初始化的singleton對象
- 獨立觀察 獲取最近一次登錄的用戶名
public volatile String lastUser; //發(fā)布的信息
public boolean authenticate(String user, String password) {
boolean valid = passwordIsValid(user, password);
if (valid) {
User u = new User();
activeUsers.add(u);
lastUser = user;
}
return valid;
}
- 開銷較低的 ‘讀-寫鎖’策略
private volatile int value;
//讀操作,沒有synchronized,提高性能
public int getValue() {
return value;
}
//寫操作,必須synchronized。因為x++不是原子操作
public synchronized int increment() {
return value++;
}