前言
- java 5之前這個(gè)關(guān)鍵字備受爭(zhēng)議,java5只有volatile才得以重生
- 因?yàn)関olatile和java的內(nèi)存模型有關(guān)(想弄清楚volatile就必須弄清相關(guān)的內(nèi)存模型)
java相關(guān)的內(nèi)存模型的概念
- 程序運(yùn)行時(shí)臨時(shí)數(shù)據(jù)存放在主存(物理內(nèi)存中)
- cpu 在告訴運(yùn)作時(shí)cpu存在高速緩存
- 就存在高速緩存和內(nèi)存之間的數(shù)據(jù)交互
- 問(wèn)題:數(shù)據(jù)的高并發(fā)同步問(wèn)題
并發(fā)編程的三個(gè)概念
原子性
一個(gè)操作或者多個(gè)操作,要么全部執(zhí)行,要么都不執(zhí)行稱為一個(gè)程序的原子性可見(jiàn)性
多個(gè)線程同時(shí)訪問(wèn)一個(gè)變量,當(dāng)一個(gè)線程改變了這個(gè)變量的值,其他線程能夠立即 看到修改的值有序性
程序執(zhí)行的順序按照代碼的編寫順序執(zhí)行(因?yàn)樵诖a執(zhí)行階段,處理器會(huì)對(duì)代碼進(jìn)行重排,但是這并不影響代碼的最終執(zhí)行結(jié)果)
java 內(nèi)存模型
從代碼層面分析前面提的三個(gè)概念
- 分析代碼的原子性操作
x = 10; //語(yǔ)句1
y = x; //語(yǔ)句2
x++; //語(yǔ)句3
x = x + 1; //語(yǔ)句4
語(yǔ)句1是直接將數(shù)值10賦值給x,也就是說(shuō)線程執(zhí)行這個(gè)語(yǔ)句的會(huì)直接將數(shù)值10寫入到工作內(nèi)存中。
語(yǔ)句2實(shí)際上包含2個(gè)操作,它先要去讀取x的值,再將x的值寫入工作內(nèi)存,雖然讀取x的值以及 將x的值寫入工作內(nèi)存 這2個(gè)操作都是原子性操作,但是合起來(lái)就不是原子性操作了。
同樣的,x++和 x = x+1包括3個(gè)操作:讀取x的值,進(jìn)行加1操作,寫入新的值。
所以上面4個(gè)語(yǔ)句只有語(yǔ)句1的操作具備原子性。
總結(jié):
java內(nèi)存模型只保證了基本讀取和賦值是原子性操作,如果要實(shí)現(xiàn)更大范圍操作的原子性,可以通過(guò)synchronized和lock來(lái)實(shí)現(xiàn),(因?yàn)殒i可以保證同一時(shí)刻只有一個(gè)線程執(zhí)行該代碼塊,從而保證了原子性)
可見(jiàn)性
1、對(duì)于可見(jiàn)性,volatile關(guān)鍵字來(lái)保證可見(jiàn)性
當(dāng)一個(gè)共享變量被volatile修飾時(shí),他會(huì)保證修改的值會(huì)立即被更新到主存,當(dāng)有其他線程讀取時(shí),他會(huì)去主存內(nèi)從新讀取
2、而普通的共享變量不能保證可見(jiàn)性,因?yàn)椴荒鼙WC什么時(shí)候?qū)懭胫齑?br> 3、利用鎖可以實(shí)現(xiàn)變量的可見(jiàn)性,因?yàn)樵趕ynchronized和lock能保證同一時(shí)刻只有一個(gè)線程獲取鎖執(zhí)行同步代碼,并且在釋放鎖之前會(huì)將變量的修改刷新到朱存當(dāng)中來(lái)保證可見(jiàn)性;有序性
在java內(nèi)存模型中,允許編譯器和處理器對(duì)指令進(jìn)行重排,但是重排過(guò)程不會(huì)影響單線程的執(zhí)行,卻會(huì)影響多線程的并發(fā)執(zhí)行順序
1、 java中 可以通過(guò)volatile關(guān)鍵字來(lái)保證有序性
2、當(dāng)然也可通過(guò)synchronized和lock來(lái)保證有序性
3、java 先天存在happens-before原則,
剖析volatile的關(guān)鍵字
一旦一個(gè)變量被volatile修飾,就具備以下兩種含義
1、 保證了不同線程對(duì)于這個(gè)變量進(jìn)行操作時(shí)的可見(jiàn)性
2、禁止指令重排
注意:維度不能保障原子性,
首先先看個(gè)代碼
//線程1
boolean stop = false;
while(!stop){
doSomething();
}
//線程2
stop = true;
很多人都會(huì)這么干,但是這個(gè)會(huì)不會(huì)出錯(cuò)呢?答案是會(huì)出錯(cuò)的,因?yàn)榫€程中的數(shù)據(jù)和全局的數(shù)據(jù)是一樣的嗎?
解決的話 就是在stop 前面添加volatile關(guān)鍵字,他會(huì)在線程刷新數(shù)據(jù)的時(shí)候立馬改變共享內(nèi)存的數(shù)據(jù)
所以說(shuō):volatile無(wú)法保證原子性
volatile保障原子性?
無(wú)法保障
如果想保障程序的原子性 可依靠sychronized或者lock 來(lái)實(shí)現(xiàn)volatile保障可見(jiàn)性
public class Test {
public volatile int inc = 0;
public void increase() {
inc++;
}
public static void main(String[] args) {
final Test test = new Test();
for(int i=0;i<10;i++){
new Thread(){
public void run() {
for(int j=0;j<1000;j++)
test.increase();
};
}.start();
}
while(Thread.activeCount()>1) //保證前面的線程都執(zhí)行完
Thread.yield();
System.out.println(test.inc);
}
}
這串代碼就能保證最后的結(jié)果是10000嗎?答案也是no
因?yàn)殡m然說(shuō)線程在執(zhí)行的時(shí)候會(huì)立馬更新主存的數(shù)據(jù),但是數(shù)據(jù)的++本身分為兩個(gè)步驟,所以無(wú)法達(dá)到理想效果
1、可采用java atomic 包下面的 atomicinteger 的增加來(lái)計(jì)算
2、利用synchronized 和 lock 來(lái)實(shí)現(xiàn)原子性
總結(jié):volatile實(shí)現(xiàn)可見(jiàn)性,是一個(gè)線程(cpu)的數(shù)據(jù)改變,他會(huì)更新主存的數(shù)據(jù)改變,同時(shí)他會(huì)讓其他線程(cpu)的數(shù)據(jù)失效。
- volatile保證有序性
線程中的順序 會(huì)以volatile為分界,按順序執(zhí)行
//x、y為非volatile變量
//flag為volatile變量
x = 2; //語(yǔ)句1
y = 0; //語(yǔ)句2
volatile flag = true; //語(yǔ)句3
x = 4; //語(yǔ)句4
y = -1; //語(yǔ)句5
分析:1、2是一組 誰(shuí)在前誰(shuí)在后不一定
但是一定會(huì)在3前,同理,3肯定在4、5的前面,
4、5誰(shuí)在前誰(shuí)在后不一定
volatile的應(yīng)用場(chǎng)景
- synchronizd關(guān)鍵字是防止多個(gè)線程同時(shí)執(zhí)行一段代碼,那么他會(huì)很影響程序執(zhí)行的效率,而volatile關(guān)鍵字在某些方面優(yōu)于synchronized,但是注意volatile關(guān)鍵字無(wú)法替代synchronized關(guān)鍵字的,因?yàn)関olatile無(wú)法保證程序的原子性,
- 通常使用volatile有以下2個(gè)條件:
1、對(duì)變量的改變不依賴于當(dāng)前值
2、對(duì)變量的依附不依賴于其他對(duì)象
示例:
volatile boolean flag = false;
while(!flag){
doSomething();
}
public void setFlag() {
flag = true;
}
volatile boolean inited = false;
//線程1:
context = loadContext();
inited = true;
//線程2:
while(!inited ){
sleep()
}
doSomethingwithconfig(context);
一般都是作為多線程標(biāo)記用的