作者: 一字馬胡
轉(zhuǎn)載標(biāo)志 【2017-11-03】
更新日志
日期 | 更新內(nèi)容 | 備注 |
---|---|---|
2017-11-03 | 添加轉(zhuǎn)載標(biāo)志 | 持續(xù)更新 |
在進(jìn)行java并發(fā)編程時骡楼,volatile和synchronized的使用是相當(dāng)廣泛的邮弹,為了安全的進(jìn)行并發(fā)編程凑队,學(xué)習(xí)和使用volatile和synchronized也是相當(dāng)有必要的谤辜。
一厉亏、volatile
在java語言中,使用多個線程來訪問共享變量是一種常見的并發(fā)場景榔袋,這就使得多個線程可能同時修改共享變量,那么限制多個線程排他的訪問共享變量就變得非常有必要了铡俐。java提供了多種方式來達(dá)到限制多線程排他的訪問共享變量的方法凰兑,但是代價也是不同的。使用volatile是一種非常輕量級的方式审丘。如果一個變量被聲明為volatile吏够,那么jvm將保證所有線程看到的都是同一個共享變量。為什么不同線程看到的變量可能不一樣呢滩报?因為為了提高處理速度锅知,cpu不直接和內(nèi)存交互,而是首先將內(nèi)存讀取到內(nèi)部緩存(L1,L2等)中脓钾,然后cpu就直接和內(nèi)部緩存通信來提高處理速度售睹。對于具有多個cpu的機(jī)器來說,不同的線程可能都在訪問某個共享變量可训,而不同的線程運(yùn)行在不同的cpu里面昌妹,所以同一個變量可能被緩存在多個cpu內(nèi)部緩存里面,如果沒有volatile來保證共享變量對于多線程是一致的話握截,就可能發(fā)生多個線程訪問到的共享變量具備不同的值飞崖,因為我們不知道cpu會在什么時候?qū)⒕彺娴闹祵懟氐絻?nèi)存中去。
在實現(xiàn)上谨胞,如果對被volatile修飾的共享變量執(zhí)行寫操作的話固歪,JVM就會向cpu發(fā)送一條Lock前綴的指令,cpu將會這個變量所在的緩存行(緩存中可以分配的最小緩存單位)寫回到內(nèi)存中去胯努。但是在多處理器的情況下牢裳,將某個cpu上的緩存行寫回到系統(tǒng)內(nèi)存之后术瓮,其他cpu上該變量的緩存還是舊的,這樣再進(jìn)行后面的操作的時候就會出現(xiàn)問題贰健,所以為了使得所有線程看到的內(nèi)容都是一致的胞四,就需要實現(xiàn)緩存一致性協(xié)議,cpu將會通過監(jiān)控總線上傳遞過來的數(shù)據(jù)來判斷自己的緩存是否過期伶椿,如果過期辜伟,就需要使得緩存失效,如果cpu再來訪問該緩存的時候脊另,就會發(fā)現(xiàn)緩存失效了导狡,這時候就會重新從內(nèi)存加載緩存。
總結(jié)一下偎痛,volatile的實現(xiàn)原則有兩條:
1旱捧、JVM的Lock前綴的指令將使得cpu緩存寫回到系統(tǒng)內(nèi)存中去
2、為了保證緩存一致性原則踩麦,在多cpu的情景下枚赡,一個cpu的緩存回寫內(nèi)存會導(dǎo)致其他的
cpu上的緩存都失效,再次訪問會重新從系統(tǒng)內(nèi)存加載新的緩存內(nèi)容谓谦。
二贫橙、synchronized
相對于volatile,synchronized就顯得比較重量級了反粥。
首先卢肃,我們應(yīng)該知道,在java中才顿,所有的對象都可以作為鎖莫湘。可以分為下面三種情況:
1郑气、普通方法同步幅垮,鎖是當(dāng)前對象
2、靜態(tài)方法同步竣贪,鎖是當(dāng)前類的Class對象
3军洼、普通塊同步,鎖是synchronize里面配置的對象
當(dāng)一個線程試圖訪問同步代碼時演怎,必須要先獲得鎖匕争,退出或者拋出異常時必須要釋放鎖。
JVM基于進(jìn)入和退出Monitor對象來實現(xiàn)方法同步和代碼塊同步爷耀,可以使用monitorenter和monitorexit指令實現(xiàn)甘桑。monitorenter指令是在編譯后插入到同步代碼塊的開始位置,而monitorexit指令則插入到方法結(jié)束和異常處,JVM保證每個monitorenter都有一個monitorexit閾值相對應(yīng)跑杭。線程執(zhí)行到monitorenter的時候铆帽,會嘗試獲得對象所對應(yīng)的monitor的鎖,然后才能獲得訪問權(quán)限德谅,synchronize使用的鎖保存在Java對象頭中爹橱。