JAVA并發(fā)編程:volatile關(guān)鍵字

本文主要為記錄和整理為主,在文章最低下會附上原文鏈接提岔。
把我遇到的知識點和問題梳理出來。

1.JAVA并發(fā)編程中的三個概念

1.原子性
2.可見性
3.有序性

原子性

原子性:即一個操作或者多個操作 要么全部執(zhí)行并且執(zhí)行的過程不會被任何因素打斷,要么就都不執(zhí)行跨细。

在Java中臭猜,對基本數(shù)據(jù)類型的變量的讀取和賦值操作是原子性操作躺酒,即這些操作是不可被中斷的,要么執(zhí)行蔑歌,要么不執(zhí)行羹应。

x = 10;         //語句1
y = x;         //語句2
x++;           //語句3
x = x + 1;     //語句4

這四個語句只有語句1是原子性的
其他三句都需要先讀取X變量的值,然后進行其他操作

那么在讀取X變量值后都有可能發(fā)生阻塞次屠,這時就破壞了原子性园匹。

也就是說雳刺,只有簡單的讀取、賦值(而且必須是將數(shù)字賦值給某個變量偎肃,變量之間的相互賦值不是原子操作)才是原子操作煞烫。

不過這里有一點需要注意:在32位平臺下,對64位數(shù)據(jù)的讀取和賦值是需要通過兩個操作來完成的累颂,不能保證其原子性滞详。但是好像在最新的JDK中,JVM已經(jīng)保證對64位數(shù)據(jù)的讀取和賦值也是原子性操作了紊馏。

保證原子性的方法:synchronize和Lock關(guān)鍵字 利用同步鎖料饥,保證一次只能一個線程對變量進行操作。

可見性

對于可見性朱监,Java提供了volatile關(guān)鍵字來保證可見性岸啡。

當(dāng)一個共享變量被volatile修飾時,它會保證修改的值會立即被更新到主存赫编,當(dāng)有其他線程需要讀取時巡蘸,它會去內(nèi)存中讀取新值。

而普通的共享變量不能保證可見性擂送,因為普通共享變量被修改之后悦荒,什么時候被寫入主存是不確定的,當(dāng)其他線程去讀取時嘹吨,此時內(nèi)存中可能還是原來的舊值搬味,因此無法保證可見性。

另外蟀拷,通過synchronized和Lock也能夠保證可見性碰纬,synchronized和Lock能保證同一時刻只有一個線程獲取鎖然后執(zhí)行同步代碼,并且在釋放鎖之前會將對變量的修改刷新到主存當(dāng)中问芬。因此可以保證可見性悦析。

有序性

在Java里面,可以通過volatile關(guān)鍵字來保證一定的“有序性”(具體原理在下一節(jié)講述)此衅。另外可以通過synchronized和Lock來保證有序性强戴,很顯然,synchronized和Lock保證每個時刻是有一個線程執(zhí)行同步代碼炕柔,相當(dāng)于是讓線程順序執(zhí)行同步代碼酌泰,自然就保證了有序性媒佣。

下面就來具體介紹下happens-before原則(先行發(fā)生原則):

程序次序規(guī)則:一個線程內(nèi)匕累,按照代碼順序,書寫在前面的操作先行發(fā)生于書寫在后面的操作
鎖定規(guī)則:一個unLock操作先行發(fā)生于后面對同一個鎖額lock操作
volatile變量規(guī)則:對一個變量的寫操作先行發(fā)生于后面對這個變量的讀操作
傳遞規(guī)則:如果操作A先行發(fā)生于操作B默伍,而操作B又先行發(fā)生于操作C欢嘿,則可以得出操作A先行發(fā)生于操作C

下面我們來解釋一下前4條規(guī)則:

對于程序次序規(guī)則來說衰琐,我的理解就是一段程序代碼的執(zhí)行在單個線程中看起來是有序的。注意炼蹦,雖然這條規(guī)則中提到“書寫在前面的操作先行發(fā)生于書寫在后面的操作”羡宙,這個應(yīng)該是程序看起來執(zhí)行的順序是按照代碼順序執(zhí)行的,因為虛擬機可能會對程序代碼進行指令重排序掐隐。雖然進行重排序狗热,但是最終執(zhí)行的結(jié)果是與程序順序執(zhí)行的結(jié)果一致的,它只會對不存在數(shù)據(jù)依賴性的指令進行重排序虑省。因此匿刮,在單個線程中,程序執(zhí)行看起來是有序執(zhí)行的探颈,這一點要注意理解熟丸。事實上,這個規(guī)則是用來保證程序在單線程中執(zhí)行結(jié)果的正確性伪节,但無法保證程序在多線程中執(zhí)行的正確性光羞。

第二條規(guī)則也比較容易理解,也就是說無論在單線程中還是多線程中怀大,同一個鎖如果出于被鎖定的狀態(tài)纱兑,那么必須先對鎖進行了釋放操作,后面才能繼續(xù)進行l(wèi)ock操作叉寂。

第三條規(guī)則是一條比較重要的規(guī)則萍启,也是后文將要重點講述的內(nèi)容。直觀地解釋就是屏鳍,如果一個線程先去寫一個變量勘纯,然后一個線程去進行讀取,那么寫入操作肯定會先行發(fā)生于讀操作钓瞭。

第四條規(guī)則實際上就是體現(xiàn)happens-before原則具備傳遞性驳遵。

JAVA內(nèi)存模型圖(JMM)

JMM內(nèi)存模型

Volatile關(guān)鍵字的兩層意思

一旦一個共享變量(類的成員變量、類的靜態(tài)成員變量)被volatile修飾之后山涡,那么就具備了兩層語義:

1)保證了不同線程對這個變量進行操作時的可見性堤结,即一個線程修改了某個變量的值,這新值對其他線程來說是立即可見的鸭丛。

2)禁止進行指令重排序竞穷。

Volatile關(guān)鍵字 能保證原子性、可見性鳞溉、有序性嗎瘾带?為什么?舉例子說明

Volatile關(guān)鍵字不能保證原子性熟菲,可以保證可見性看政,能保證部分的有序性朴恳。

比如在多線程環(huán)境下對變量i進行自增操作,假設(shè)初始時i的值為0允蚣,那么操作后i可能為1.

這里有2種方式去理解:
1.首先A線程讀取變量i于颖,值為0 然后發(fā)生阻塞。此時線程B讀取i的值也為0 然后自增操作嚷兔。i=1 把i的最新值1更新到本地共享變量的副本森渐,然后再刷新到主內(nèi)存中去。然后此時再回到A線程冒晰,A線程這個時候也對自己的0值進行加1操作章母,然后更新回副本刷新到主存中去。此時主存中i=1翩剪。這里關(guān)鍵的一個點就是乳怎,當(dāng)線程B進行寫操作后,會使得其他線程的緩存行失效前弯,然后其他線程就會去主存中讀取最新的值蚪缀,這個沒錯。但是 線程A在一開始的時候已經(jīng)把值0從緩存行入棧到自己的棧頂了(底層的指令集的操作)恕出,也就不需要再去讀取緩存行所以緩存行的失效對線程A沒有作用询枚。

2.首先A線程讀取變量i,值為0 然后發(fā)生阻塞浙巫。此時線程B讀取i的值也為0金蜀,然后進行自增操作值為1.然后在進行更新變量副本之前,線程B阻塞的畴。然后回到線程A渊抄,A也進行自增然后把最新值1更新到變量副本刷新回到主內(nèi)存中去。此時回到線程B丧裁,線程B繼續(xù)更新變量副本然后把值刷新到主內(nèi)存中去還是1.

保證可見性是對的护桦,因為當(dāng)volatile關(guān)鍵字修飾的變量被寫操作之后。就會對緩存行失效煎娇,其他的線程再次讀取都會使用到最新的值二庵,保證了可見性。

為什么說是部分的有序性呢缓呛?
因為
1)當(dāng)程序執(zhí)行到volatile變量的讀操作或者寫操作時催享,在其前面的操作的更改肯定全部已經(jīng)進行,且結(jié)果已經(jīng)對后面的操作可見哟绊;在其后面的操作肯定還沒有進行因妙;

2)在進行指令優(yōu)化時,不能將在對volatile變量訪問的語句放在其后面執(zhí)行,也不能把volatile變量后面的語句放到其前面執(zhí)行兰迫。

//x、y為非volatile變量
//flag為volatile變量
 
x = 2;        //語句1
y = 0;        //語句2
flag = true;  //語句3
x = 4;         //語句4
y = -1;       //語句5

由于flag變量為volatile變量炬称,那么在進行指令重排序的過程的時候汁果,不會將語句3放到語句1、語句2前面玲躯,也不會講語句3放到語句4据德、語句5后面。但是要注意語句1和語句2的順序跷车、語句4和語句5的順序是不作任何保證的棘利。

并且volatile關(guān)鍵字能保證,執(zhí)行到語句3時朽缴,語句1和語句2必定是執(zhí)行完畢了的善玫,且語句1和語句2的執(zhí)行結(jié)果對語句3、語句4密强、語句5是可見的茅郎。

volatile關(guān)鍵字的一些使用場景

使用volatile必須具備以下2個條件:

1)對變量的寫操作不依賴于當(dāng)前值

2)該變量沒有包含在具有其他變量的不變式中

實際上,這些條件表明或渤,可以被寫入 volatile 變量的這些有效值獨立于任何程序的狀態(tài)系冗,包括變量的當(dāng)前狀態(tài)。

事實上薪鹦,我的理解就是上面的2個條件需要保證操作是原子性操作掌敬,才能保證使用volatile關(guān)鍵字的程序在并發(fā)時能夠正確執(zhí)行。

1.標(biāo)記狀態(tài)量

volatile boolean flag = false;
 
while(!flag){
    doSomething();
}
 
public void setFlag() {
    flag = true;
}

保證了執(zhí)行到inited賦值為true時池磁,Context已經(jīng)初始化完成奔害,線程2再使用的時候就不會出現(xiàn)錯誤

volatile boolean inited = false;
//線程1:
context = loadContext();  
inited = true;            
 
//線程2:
while(!inited ){
sleep()
}
doSomethingwithconfig(context);

2.單例模式double check

為什么要加volatile關(guān)鍵字,就是為了保證instance的初始化完成之后才會被使用地熄,以免報錯舀武。如果不使用,可能會出現(xiàn)离斩,線程A先new了一個對象 分配了內(nèi)存地址银舱,但是初始化對象的工作沒有完成。此時線程B進來跛梗,instance不為空寻馏。線程B持有instance然后使用的時候報錯。

class Singleton{
    private volatile static Singleton instance = null;
     
    private Singleton() {
         
    }
     
    public static Singleton getInstance() {
        if(instance==null) {
            synchronized (Singleton.class) {
                if(instance==null)
                    instance = new Singleton();
            }
        }
        return instance;
    }
}

自增操作保證原子性的方法有哪些?

用synchronize關(guān)鍵字

public synchronized void increase() {
        inc++;
    }

用lock

public class Test {
    public  int inc = 0;
    Lock lock = new ReentrantLock();
    
    public  void increase() {
        lock.lock();
        try {
            inc++;
        } finally{
            lock.unlock();
        }
    }
}

用原子操作類

public class Test {
    public  AtomicInteger inc = new AtomicInteger();
     
    public  void increase() {
        inc.getAndIncrement();
    }
}

總結(jié)一下synchronize lock volatile 和原子性 可見性 有序性的關(guān)系

synchronize和lock能保證可見性的原因是核偿,在釋放鎖之前會將對變量的修改刷新到主存當(dāng)中诚欠。

原文參考鏈接:
Java并發(fā)編程:volatile關(guān)鍵字解析

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子轰绵,更是在濱河造成了極大的恐慌粉寞,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件左腔,死亡現(xiàn)場離奇詭異唧垦,居然都是意外死亡,警方通過查閱死者的電腦和手機液样,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進店門振亮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人鞭莽,你說我怎么就攤上這事坊秸。” “怎么了澎怒?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵褒搔,是天一觀的道長。 經(jīng)常有香客問我喷面,道長站超,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任乖酬,我火速辦了婚禮死相,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘咬像。我一直安慰自己算撮,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布县昂。 她就那樣靜靜地躺著肮柜,像睡著了一般。 火紅的嫁衣襯著肌膚如雪倒彰。 梳的紋絲不亂的頭發(fā)上审洞,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天,我揣著相機與錄音待讳,去河邊找鬼芒澜。 笑死,一個胖子當(dāng)著我的面吹牛创淡,可吹牛的內(nèi)容都是我干的痴晦。 我是一名探鬼主播,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼琳彩,長吁一口氣:“原來是場噩夢啊……” “哼誊酌!你這毒婦竟也來了部凑?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤碧浊,失蹤者是張志新(化名)和其女友劉穎涂邀,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體箱锐,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡比勉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了瑞躺。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡兴想,死狀恐怖幢哨,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情嫂便,我是刑警寧澤捞镰,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站毙替,受9級特大地震影響岸售,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜厂画,卻給世界環(huán)境...
    茶點故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一凸丸、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧袱院,春花似錦屎慢、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至欲虚,卻和暖如春集灌,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背复哆。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工欣喧, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人梯找。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓续誉,卻偏偏與公主長得像,于是被迫代替她去往敵國和親初肉。 傳聞我的和親對象是個殘疾皇子酷鸦,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,472評論 2 348

推薦閱讀更多精彩內(nèi)容