本篇內(nèi)容主要講解“JVM內(nèi)存模型、可見性、指令重排序”,感興趣的朋友不妨來看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來帶大家學(xué)習(xí)“JVM內(nèi)存模型、可見性、指令重排序”吧!
創(chuàng)新互聯(lián)堅(jiān)持“要么做到,要么別承諾”的工作理念,服務(wù)領(lǐng)域包括:網(wǎng)站建設(shè)、成都網(wǎng)站建設(shè)、企業(yè)官網(wǎng)、英文網(wǎng)站、手機(jī)端網(wǎng)站、網(wǎng)站推廣等服務(wù),滿足客戶于互聯(lián)網(wǎng)時(shí)代的連江網(wǎng)站設(shè)計(jì)、移動(dòng)媒體設(shè)計(jì)的需求,幫助企業(yè)找到有效的互聯(lián)網(wǎng)解決方案。努力成為您成熟可靠的網(wǎng)絡(luò)建設(shè)合作伙伴!
內(nèi)存模型
首先我們思考一下一個(gè)java線程要向另外一個(gè)線程進(jìn)行通信,應(yīng)該怎么做,我們?cè)侔研枨竺鞔_一點(diǎn),一個(gè)java線程對(duì)一個(gè)變量的更新怎么通知到另外一個(gè)線程呢?我們知道java當(dāng)中的實(shí)例對(duì)象、數(shù)組元素都放在java堆中,java堆是線程共享的。(我們這里把java堆稱為主內(nèi)存),而每一個(gè)線程都是自己私有的內(nèi)存空間(稱為工作內(nèi)存),如果線程1要向線程2通信,一定會(huì)經(jīng)過類似的流程:
1、 線程1將自己工作內(nèi)存中的X更新為1并刷新到主內(nèi)存中;
2、 線程2從主內(nèi)存讀取變量X=1,更新到自己的工作內(nèi)存中,從而線程2讀取的X就是線程1更新后的值。
從上面的流程看出線程之間的通信都需要經(jīng)過主內(nèi)存,而主內(nèi)存與工作內(nèi)存的交互,則需要Java內(nèi)存模型(JMM)來管理器。下圖演示了JMM如何管理主內(nèi)存和工作內(nèi)存:
當(dāng)線程1需要將一個(gè)更新后的變量值刷新到主內(nèi)存中時(shí),需要經(jīng)過兩個(gè)步驟:
1、 工作內(nèi)存執(zhí)行store操作;
2、 主內(nèi)存執(zhí)行write操作;
完成這兩步即可將工作內(nèi)存中的變量值刷新到主內(nèi)存,即線程1工作內(nèi)存和主內(nèi)存的變量值保持一致;
當(dāng)線程2需要從主內(nèi)存中讀取變量的最新值時(shí),同樣需要經(jīng)過兩個(gè)步驟:
1、主內(nèi)存執(zhí)行read操作,將變量值從主內(nèi)存中讀取出來;
2、工作內(nèi)存執(zhí)行l(wèi)oad操作,將讀取出來的變量值更新到本地內(nèi)存的副本;
完成這兩步,線程2的變量和主內(nèi)存的變量值就保持一致了。
可見性
Java中有一個(gè)關(guān)鍵字volatile,它有什么用呢?這個(gè)答案其實(shí)就在上述java線程間通信機(jī)制中,我們想象一下,由于工作內(nèi)存這個(gè)中間層的出現(xiàn),線程1和線程2必然存在延遲的問題,例如線程1在工作內(nèi)存中更新了變量,但還沒刷新到主內(nèi)存,而此時(shí)線程2獲取到的變量值就是未更新的變量值,又或者線程1成功將變量更新到主內(nèi)存,但線程2依然使用自己工作內(nèi)存中的變量值,同樣會(huì)出問題。不管出現(xiàn)哪種情況都可能導(dǎo)致線程間的通信不能達(dá)到預(yù)期的目的。例如以下例子:
//線程1 boolean stop = false; while(!stop){ doSomething(); } //線程2
stop
= true;
這個(gè)經(jīng)典的例子表示線程2通過修改stop的值,控制線程1中斷,但在真實(shí)環(huán)境中可能會(huì)出現(xiàn)意想不到的結(jié)果,線程2在執(zhí)行之后,線程1并沒有立刻中斷甚至一直不會(huì)中斷。出現(xiàn)這種現(xiàn)象的原因就是線程2對(duì)線程1的變量更新無法第一時(shí)間獲取到。
但這一切等到Volatile出現(xiàn)后,再也不是問題,Volatile保證兩件事:
1、 線程1工作內(nèi)存中的變量更新會(huì)強(qiáng)制立即寫入到主內(nèi)存;
2、 線程2工作內(nèi)存中的變量會(huì)強(qiáng)制立即失效,這使得線程2必須去主內(nèi)存中獲取最新的變量值。
所以這就理解了Volatile保證了變量的可見性,因?yàn)榫€程1對(duì)變量的修改能第一時(shí)間讓線程2可見。
指令重排序
關(guān)于指令排序我們先看一段代碼:
int a = 0;?boolean flag = false;
//線程1
public void writer() {
a = 1;
flag = true;
}
//線程2
public void reader() {
if (flag) {
int i= a+1;
...... }
}
線程1依次執(zhí)行a=1,flag=true;線程2判斷到flag==true后,設(shè)置i=a+1,根據(jù)代碼語義,我們可能會(huì)推斷此時(shí)i的值等于2,因?yàn)榫€程2在判斷flag==true時(shí),線程1已經(jīng)執(zhí)行了a=1;所以i的值等于a+1=1+1=2;但真實(shí)情況卻不一定如此,引起這個(gè)問題的原因是線程1內(nèi)部的兩條語句a=1;flag=true;可能被重新排序執(zhí)行,如圖:
這就是指令重排序的簡(jiǎn)單演示,兩個(gè)賦值語句盡管他們的代碼順序是一前一后,但真正執(zhí)行時(shí)卻不一定按照代碼順序執(zhí)行。你可能會(huì)說,有這個(gè)指令重排序那不是亂套了嗎?我寫的程序都不按我的代碼流程走,這怎么玩?這個(gè)你可以放心,你的程序不會(huì)亂套,因?yàn)閖ava和CPU、內(nèi)存之間都有一套嚴(yán)格的指令重排序規(guī)則,哪些可以重排,哪些不能重排都有規(guī)矩的。下列流程演示了一個(gè)java程序從編譯到執(zhí)行會(huì)經(jīng)歷哪些重排序:
在這個(gè)流程中第一步屬于編譯器重排查,編譯器重排序會(huì)按JMM的規(guī)范嚴(yán)格進(jìn)行,換言之編譯器重排序一般不會(huì)對(duì)程序的正確邏輯造成影響。第二、三步屬于處理器重排序,處理器重排序JMM就不好管了,怎么辦呢?它會(huì)要求java編譯器在生成指令時(shí)加入內(nèi)存屏障,內(nèi)存屏障是什么?你可以理解為一個(gè)不透風(fēng)的保護(hù)罩,把不能重排序的java指令保護(hù)起來,那么處理器在遇到內(nèi)存屏障保護(hù)的指令時(shí)就不會(huì)對(duì)它進(jìn)行重排序了。關(guān)于在哪些地方該加入內(nèi)存屏障,內(nèi)存屏障有哪些種類,各有什么作用,這些知識(shí)點(diǎn)這里就不再闡述了。可以參考JVM規(guī)范相關(guān)資料。
下面介紹一下在同一個(gè)線程中,不會(huì)被重排序的邏輯:
這三種情況中,任意改變一個(gè)代碼的順序,結(jié)果都會(huì)大不相同,對(duì)于這樣的邏輯代碼,是不會(huì)被重排序的。注意這是指單線程中不會(huì)被重排序,如果在多線程環(huán)境下,還是會(huì)產(chǎn)生邏輯問題,例如我們一開始舉的例子。
到此,相信大家對(duì)“JVM內(nèi)存模型、可見性、指令重排序”有了更深的了解,不妨來實(shí)際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!
文章標(biāo)題:JVM內(nèi)存模型、可見性、指令重排序
URL標(biāo)題:http://jinyejixie.com/article8/psicip.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供品牌網(wǎng)站制作、App開發(fā)、小程序開發(fā)、網(wǎng)站設(shè)計(jì)公司、App設(shè)計(jì)、關(guān)鍵詞優(yōu)化
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)