特征
被volatile修飾的變量野建,具有兩個(gè)特征
- 保證可見性
- 不保證原子性
- 禁止指令重排序
關(guān)于內(nèi)存可見性属划、原子性、有序性候生,先來了解一下內(nèi)存模型吧~
java內(nèi)存模型(JMM)
- JMM定義了線程和主內(nèi)存之間的抽相關(guān)
- 每個(gè)線程都會(huì)有一個(gè)私有的本地內(nèi)存同眯,存儲(chǔ)了共享變量的副本
- 共享變量存儲(chǔ)再主內(nèi)存中
- image
特性
- 原子性
- 一個(gè)操作要么全部執(zhí)行并且執(zhí)行的過程不會(huì)被打斷,要么就不執(zhí)行(有點(diǎn)像事務(wù))
- 下面舉個(gè)例子
i = 0; //是原子操作
j = i ; //不是唯鸭! 包含兩個(gè)操作 1.讀取i 2.賦值給j
i++; //不是须蜗!三個(gè)操作 1.讀取i 2.+1 3.賦值給i
volatile是無(wú)法保證復(fù)合操作的原子性。想在多線程環(huán)境下保證原子性目溉,可以通過鎖明肮、synchronized來確保
- 可見性
- 多線程訪問一個(gè)變量時(shí),一個(gè)線程修改變量的值缭付,其他線程能立即看到柿估。
- 但是,多線程環(huán)境下陷猫,一個(gè)線程修改變量對(duì)其他線程是不可見的秫舌!
volatile可以保證可見性。當(dāng)一個(gè)變量被volatile修飾之后烙丛,該變量被修改后立即更新到內(nèi)存中,讀取的時(shí)候會(huì)直接從內(nèi)存中讀取羔味。
- 有序性
- 執(zhí)行的順序按照代碼的先后順序執(zhí)行
- 在java內(nèi)存模型中河咽,為了效率,是允許處理器對(duì)指令進(jìn)行重排序的
volatile禁止指令重排序赋元,來保證一定的有序性
指令重排序:是JVM為了優(yōu)化指令忘蟹,提高程序運(yùn)行效率,在不影響單線程程序執(zhí)行結(jié)果的前提下搁凸,盡可能地提高并行度媚值。注意是單線程,多線程情況下會(huì)有問題啊
原理
在jvm底層 是采用‘內(nèi)存屏障’來實(shí)現(xiàn)的
-
內(nèi)存屏障 (Memory Barrier)
- 又叫內(nèi)存柵欄护糖,是一個(gè)cpu指令
- 插入一條MB褥芒,會(huì)告訴編譯器和cpu,什么指令都不能和這條MB指令重排序
- MB會(huì)強(qiáng)制刷出各種CPU cache,如一個(gè)Write-Barrier將刷出所有再Barrier之前寫入cache的數(shù)據(jù)嫡良,因此cpu上的線程都能讀取到這些數(shù)據(jù)的最新版本
-
****如果一個(gè)變量是volatile修飾的锰扶,JMM會(huì)再寫入這個(gè)字段之后插入Write-Barrier指令,在讀這個(gè)字段之前插入Read-Barrier指令****寝受,意味著:
- 一個(gè)線程寫入變量A后坷牛,任何線程都可以拿到最新值
-
happens-before
- 兩個(gè)操作間具有h-b關(guān)系,并不以為著前一個(gè)操作必須要在后一個(gè)操作之前執(zhí)行很澄。
- 僅僅要求前一個(gè)操作的執(zhí)行結(jié)果京闰,對(duì)后一個(gè)操作可見颜及。且前一個(gè)操作按順序排在后一個(gè)操作之前。
應(yīng)用場(chǎng)景
- 狀態(tài)量標(biāo)記
int a = 0;
//修改后立刻對(duì)線程可見 比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
}
}
- 單例模式的實(shí)現(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進(jìn)入到//A處,但在構(gòu)造函數(shù)執(zhí)行之前捐迫。使實(shí)例成為非null
2.線程2進(jìn)入//B處乾翔,實(shí)例不為null,將instance引用返回施戴。返回了一個(gè)構(gòu)造完整但部分初始化的singleton對(duì)象
- 獨(dú)立觀察 獲取最近一次登錄的用戶名
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雷则。因?yàn)閤++不是原子操作
public synchronized int increment() {
return value++;
}