前言
- java 5之前這個關(guān)鍵字備受爭議切威,java5只有volatile才得以重生
- 因為volatile和java的內(nèi)存模型有關(guān)(想弄清楚volatile就必須弄清相關(guān)的內(nèi)存模型)
java相關(guān)的內(nèi)存模型的概念
- 程序運行時臨時數(shù)據(jù)存放在主存(物理內(nèi)存中)
- cpu 在告訴運作時cpu存在高速緩存
- 就存在高速緩存和內(nèi)存之間的數(shù)據(jù)交互
- 問題:數(shù)據(jù)的高并發(fā)同步問題
并發(fā)編程的三個概念
原子性
一個操作或者多個操作,要么全部執(zhí)行芽死,要么都不執(zhí)行稱為一個程序的原子性可見性
多個線程同時訪問一個變量,當一個線程改變了這個變量的值次洼,其他線程能夠立即 看到修改的值有序性
程序執(zhí)行的順序按照代碼的編寫順序執(zhí)行(因為在代碼執(zhí)行階段关贵,處理器會對代碼進行重排,但是這并不影響代碼的最終執(zhí)行結(jié)果)
java 內(nèi)存模型
從代碼層面分析前面提的三個概念
- 分析代碼的原子性操作
x = 10; //語句1
y = x; //語句2
x++; //語句3
x = x + 1; //語句4
語句1是直接將數(shù)值10賦值給x卖毁,也就是說線程執(zhí)行這個語句的會直接將數(shù)值10寫入到工作內(nèi)存中揖曾。
語句2實際上包含2個操作,它先要去讀取x的值亥啦,再將x的值寫入工作內(nèi)存炭剪,雖然讀取x的值以及 將x的值寫入工作內(nèi)存 這2個操作都是原子性操作,但是合起來就不是原子性操作了翔脱。
同樣的念祭,x++和 x = x+1包括3個操作:讀取x的值,進行加1操作碍侦,寫入新的值粱坤。
所以上面4個語句只有語句1的操作具備原子性。
總結(jié):
java內(nèi)存模型只保證了基本讀取和賦值是原子性操作瓷产,如果要實現(xiàn)更大范圍操作的原子性站玄,可以通過synchronized和lock來實現(xiàn),(因為鎖可以保證同一時刻只有一個線程執(zhí)行該代碼塊濒旦,從而保證了原子性)
可見性
1傻咖、對于可見性星爪,volatile關(guān)鍵字來保證可見性
當一個共享變量被volatile修飾時,他會保證修改的值會立即被更新到主存,當有其他線程讀取時谆级,他會去主存內(nèi)從新讀取
2、而普通的共享變量不能保證可見性匕得,因為不能保證什么時候?qū)懭胫齑?br> 3掠河、利用鎖可以實現(xiàn)變量的可見性,因為在synchronized和lock能保證同一時刻只有一個線程獲取鎖執(zhí)行同步代碼灯节,并且在釋放鎖之前會將變量的修改刷新到朱存當中來保證可見性循头;有序性
在java內(nèi)存模型中,允許編譯器和處理器對指令進行重排炎疆,但是重排過程不會影響單線程的執(zhí)行卡骂,卻會影響多線程的并發(fā)執(zhí)行順序
1、 java中 可以通過volatile關(guān)鍵字來保證有序性
2形入、當然也可通過synchronized和lock來保證有序性
3全跨、java 先天存在happens-before原則,
剖析volatile的關(guān)鍵字
一旦一個變量被volatile修飾亿遂,就具備以下兩種含義
1浓若、 保證了不同線程對于這個變量進行操作時的可見性
2盒使、禁止指令重排
注意:維度不能保障原子性,
首先先看個代碼
//線程1
boolean stop = false;
while(!stop){
doSomething();
}
//線程2
stop = true;
很多人都會這么干七嫌,但是這個會不會出錯呢少办?答案是會出錯的,因為線程中的數(shù)據(jù)和全局的數(shù)據(jù)是一樣的嗎诵原?
解決的話 就是在stop 前面添加volatile關(guān)鍵字英妓,他會在線程刷新數(shù)據(jù)的時候立馬改變共享內(nèi)存的數(shù)據(jù)
所以說:volatile無法保證原子性
volatile保障原子性?
無法保障
如果想保障程序的原子性 可依靠sychronized或者lock 來實現(xiàn)volatile保障可見性
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
因為雖然說線程在執(zhí)行的時候會立馬更新主存的數(shù)據(jù)蔓纠,但是數(shù)據(jù)的++本身分為兩個步驟,所以無法達到理想效果
1吗蚌、可采用java atomic 包下面的 atomicinteger 的增加來計算
2腿倚、利用synchronized 和 lock 來實現(xiàn)原子性
總結(jié):volatile實現(xiàn)可見性,是一個線程(cpu)的數(shù)據(jù)改變蚯妇,他會更新主存的數(shù)據(jù)改變敷燎,同時他會讓其他線程(cpu)的數(shù)據(jù)失效。
- volatile保證有序性
線程中的順序 會以volatile為分界箩言,按順序執(zhí)行
//x硬贯、y為非volatile變量
//flag為volatile變量
x = 2; //語句1
y = 0; //語句2
volatile flag = true; //語句3
x = 4; //語句4
y = -1; //語句5
分析:1、2是一組 誰在前誰在后不一定
但是一定會在3前陨收,同理饭豹,3肯定在4、5的前面务漩,
4拄衰、5誰在前誰在后不一定
volatile的應(yīng)用場景
- synchronizd關(guān)鍵字是防止多個線程同時執(zhí)行一段代碼,那么他會很影響程序執(zhí)行的效率饵骨,而volatile關(guān)鍵字在某些方面優(yōu)于synchronized翘悉,但是注意volatile關(guān)鍵字無法替代synchronized關(guān)鍵字的,因為volatile無法保證程序的原子性宏悦,
- 通常使用volatile有以下2個條件:
1镐确、對變量的改變不依賴于當前值
2、對變量的依附不依賴于其他對象
示例:
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);
一般都是作為多線程標記用的