本人免費整理了Java高級資料,涵蓋了Java、redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo高并發(fā)分布式等教程,一共30G,需要自己領取。
傳送門:https://mp.weixin.qq.com/s/JzddfH-7yNudmkjT0IRL8Q
在上一篇文章中我們深入理解了java關鍵字synchronized,我們知道在java中還有一大神器就是關鍵volatile,可以說是和synchronized各領風騷,其中奧妙,我們來共同探討下。
成都創(chuàng)新互聯(lián)公司專注于納雍企業(yè)網(wǎng)站建設,成都響應式網(wǎng)站建設公司,購物商城網(wǎng)站建設。納雍網(wǎng)站建設公司,為納雍等地區(qū)提供建站服務。全流程按需策劃,專業(yè)設計,全程項目跟蹤,成都創(chuàng)新互聯(lián)公司專業(yè)和態(tài)度為您提供的服務
通過上一篇的文章我們了解到synchronized是阻塞式同步,在線程競爭激烈的情況下會升級為重量級鎖。而volatile就可以說是java虛擬機提供的最輕量級的同步機制。但它同時不容易被正確理解,也至于在并發(fā)編程中很多程序員遇到線程安全的問題就會使用synchronized。Java內存模型告訴我們,各個線程會將共享變量從主內存中拷貝到工作內存,然后執(zhí)行引擎會基于工作內存中的數(shù)據(jù)進行操作處理。
線程在工作內存進行操作后何時會寫到主內存中?這個時機對普通變量是沒有規(guī)定的,而針對volatile修飾的變量給java虛擬機特殊的約定,線程對volatile變量的修改會立刻被其他線程所感知,即不會出現(xiàn)數(shù)據(jù)臟讀的現(xiàn)象,從而保證數(shù)據(jù)的“可見性”。
現(xiàn)在我們有了一個大概的印象就是:被volatile修飾的變量能夠保證每個線程能夠獲取該變量的最新值,從而避免出現(xiàn)數(shù)據(jù)臟讀的現(xiàn)象。
volatile是怎樣實現(xiàn)了?比如一個很簡單的Java代碼:
instance = new Instancce() //instance是volatile變量
在生成匯編代碼時會在volatile修飾的共享變量進行寫操作的時候會多出Lock前綴的指令(具體的大家可以使用一些工具去看一下,這里我就只把結果說出來)。我們想這個Lock指令肯定有神奇的地方,那么Lock前綴的指令在多核處理器下會發(fā)現(xiàn)什么事情了?主要有這兩個方面的影響:
將當前處理器緩存行的數(shù)據(jù)寫回系統(tǒng)內存;
這個寫回內存的操作會使得其他CPU里緩存了該內存地址的數(shù)據(jù)無效
為了提高處理速度,處理器不直接和內存進行通信,而是先將系統(tǒng)內存的數(shù)據(jù)讀到內部緩存(L1,L2或其他)后再進行操作,但操作完不知道何時會寫到內存。如果對聲明了volatile的變量進行寫操作,JVM就會向處理器發(fā)送一條Lock前綴的指令,將這個變量所在緩存行的數(shù)據(jù)寫回到系統(tǒng)內存。但是,就算寫回到內存,如果其他處理器緩存的值還是舊的,再執(zhí)行計算操作就會有問題。所以,在多處理器下,為了保證各個處理器的緩存是一致的,就會實現(xiàn)緩存一致性協(xié)議,每個處理器通過嗅探在總線上傳播的數(shù)據(jù)來檢查自己緩存的值是不是過期了,當處理器發(fā)現(xiàn)自己緩存行對應的內存地址被修改,就會將當前處理器的緩存行設置成無效狀態(tài),當處理器對這個數(shù)據(jù)進行修改操作的時候,會重新從系統(tǒng)內存中把數(shù)據(jù)讀到處理器緩存里。因此,經(jīng)過分析我們可以得出如下結論:
Lock前綴的指令會引起處理器緩存寫回內存;
一個處理器的緩存回寫到內存會導致其他處理器的緩存失效;
當處理器發(fā)現(xiàn)本地緩存失效后,就會從內存中重讀該變量數(shù)據(jù),即可以獲取當前最新值。
這樣針對volatile變量通過這樣的機制就使得每個線程都能獲得該變量的最新值。
經(jīng)過上面的分析,我們已經(jīng)知道了volatile變量可以通過緩存一致性協(xié)議保證每個線程都能獲得最新值,即滿足數(shù)據(jù)的“可見性”。我們繼續(xù)延續(xù)上一篇分析問題的方式(我一直認為思考問題的方式是屬于自己,也才是最重要的,也在不斷培養(yǎng)這方面的能力),我一直將并發(fā)分析的切入點分為兩個核心,三大性質。兩大核心:JMM內存模型(主內存和工作內存)以及happens-before;三條性質:原子性,可見性,有序性(關于三大性質的總結在以后得文章會和大家共同探討)。廢話不多說,先來看兩個核心之一:volatile的happens-before關系。
在六條happens-before規(guī)則中有一條是:volatile變量規(guī)則:對一個volatile域的寫,happens-before于任意后續(xù)對這個volatile域的讀。下面我們結合具體的代碼,我們利用這條規(guī)則推導下:
public?class?VolatileExample?{ ????private?int?a?=?0; ????private?volatile?boolean?flag?=?false; ????public?void?writer(){ ????????a?=?1;??????????//1 ????????flag?=?true;???//2 ????} ????public?void?reader(){ ????????if(flag){??????//3 ????????????int?i?=?a;?//4 ????????} ????} }
上面的實例代碼對應的happens-before關系如下圖所示:
加鎖線程A先執(zhí)行writer方法,然后線程B執(zhí)行reader方法圖中每一個箭頭兩個節(jié)點就代碼一個happens-before關系,黑色的代表根據(jù)程序順序規(guī)則推導出來,紅色的是根據(jù)volatile變量的寫happens-before 于任意后續(xù)對volatile變量的讀,而藍色的就是根據(jù)傳遞性規(guī)則推導出來的。
這里的2 happen-before 3,同樣根據(jù)happens-before規(guī)則定義:如果A happens-before B,則A的執(zhí)行結果對B可見,并且A的執(zhí)行順序先于B的執(zhí)行順序,我們可以知道操作2執(zhí)行結果對操作3來說是可見的,也就是說當線程A將volatile變量 flag更改為true后線程B就能夠迅速感知。
還是按照兩個核心的分析方式,分析完happens-before關系后我們現(xiàn)在就來進一步分析volatile的內存語義(按照這種方式去學習,會不會讓大家對知識能夠把握的更深,而不至于不知所措,如果大家認同我的這種方式,不妨給個贊,小弟在此謝過,對我是個鼓勵)。還是以上面的代碼為例,假設線程A先執(zhí)行writer方法,線程B隨后執(zhí)行reader方法,初始時線程的本地內存中flag和a都是初始狀態(tài),下圖是線程A執(zhí)行volatile寫后的狀態(tài)圖。
當volatile變量寫后,線程中本地內存中共享變量就會置為失效的狀態(tài),因此線程B再需要讀取從主內存中去讀取該變量的最新值。下圖就展示了線程B讀取同一個volatile變量的內存變化示意圖。
從橫向來看,線程A和線程B之間進行了一次通信,線程A在寫volatile變量時,實際上就像是給B發(fā)送了一個消息告訴線程B你現(xiàn)在的值都是舊的了,然后線程B讀這個volatile變量時就像是接收了線程A剛剛發(fā)送的消息。既然是舊的了,那線程B該怎么辦了?自然而然就只能去主內存去取啦。
好的,我們現(xiàn)在兩個核心:happens-before以及內存語義現(xiàn)在已經(jīng)都了解清楚了。是不是還不過癮,突然發(fā)現(xiàn)原來自己會這么愛學習(微笑臉),那我們下面就再來一點干貨----volatile內存語義的實現(xiàn)。
我們都知道,為了性能優(yōu)化,JMM在不改變正確語義的前提下,會允許編譯器和處理器對指令序列進行重排序,那如果想阻止重排序要怎么辦了?答案是可以添加內存屏障。
內存屏障
JMM內存屏障分為四類見下圖,
java編譯器會在生成指令系列時在適當?shù)奈恢脮迦雰却嫫琳现噶顏斫固囟愋偷奶幚砥髦嘏判?。為了實現(xiàn)volatile的內存語義,JMM會限制特定類型的編譯器和處理器重排序,JMM會針對編譯器制定volatile重排序規(guī)則表:
"NO"表示禁止重排序。為了實現(xiàn)volatile內存語義時,編譯器在生成字節(jié)碼時,會在指令序列中插入內存屏障來禁止特定類型的處理器重排序。對于編譯器來說,發(fā)現(xiàn)一個最優(yōu)布置來最小化插入屏障的總數(shù)幾乎是不可能的,為此,JMM采取了保守策略:
在每個volatile寫操作的前面插入一個StoreStore屏障;
在每個volatile寫操作的后面插入一個StoreLoad屏障;
在每個volatile讀操作的后面插入一個LoadLoad屏障;
在每個volatile讀操作的后面插入一個LoadStore屏障。
需要注意的是:volatile寫是在前面和后面分別插入內存屏障,而volatile讀操作是在后面插入兩個內存屏障
StoreStore屏障:禁止上面的普通寫和下面的volatile寫重排序;
StoreLoad屏障:防止上面的volatile寫與下面可能有的volatile讀/寫重排序
LoadLoad屏障:禁止下面所有的普通讀操作和上面的volatile讀重排序
LoadStore屏障:禁止下面所有的普通寫操作和上面的volatile讀重排序
下面以兩個示意圖進行理解,圖片摘自相當好的一本書《java并發(fā)編程的藝術》。
我們現(xiàn)在已經(jīng)理解volatile的精華了,文章開頭的那個問題我想現(xiàn)在我們都能給出答案了。更正后的代碼為:
public?class?VolatileDemo?{ ????private?static?volatile?boolean?isOver?=?false; ????public?static?void?main(String[]?args)?{ ????????Thread?thread?=?new?Thread(new?Runnable()?{ ????????????@Override ????????????public?void?run()?{ ????????????????while?(!isOver)?; ????????????} ????????}); ????????thread.start(); ????????try?{ ????????????Thread.sleep(500); ????????}?catch?(InterruptedException?e)?{ ????????????e.printStackTrace(); ????????} ????????isOver?=?true; ????} }
注意不同點,現(xiàn)在已經(jīng)將isOver設置成了volatile變量,這樣在main線程中將isOver改為了true后,thread的工作內存該變量值就會失效,從而需要再次從主內存中讀取該值,現(xiàn)在能夠讀出isOver最新值為true從而能夠結束在thread里的死循環(huán),從而能夠順利停止掉thread線程。
文章名稱:徹底理解volatile,領悟其中奧妙
當前URL:http://jinyejixie.com/article4/iehgoe.html
成都網(wǎng)站建設公司_創(chuàng)新互聯(lián),為您提供關鍵詞優(yōu)化、品牌網(wǎng)站設計、營銷型網(wǎng)站建設、定制網(wǎng)站、App設計、電子商務
聲明:本網(wǎng)站發(fā)布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經(jīng)允許不得轉載,或轉載時需注明來源: 創(chuàng)新互聯(lián)