java關(guān)鍵字-volatile

前言
  • 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)記用的

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容