前些日子寫(xiě)了一篇文章分析Java運(yùn)行時(shí)內(nèi)存區(qū)域,讓我們對(duì)于Java內(nèi)存有了基本的認(rèn)識(shí),很多人常常會(huì)把兩者混淆。經(jīng)過(guò)本篇文章的介紹,相信大家會(huì)更加熟悉和了解兩者之間的區(qū)別。
Java內(nèi)存模型基礎(chǔ)
Java內(nèi)存模型(JMM),從抽象的角度來(lái)看其定義了Java主內(nèi)存與線程本地內(nèi)存之間的抽象關(guān)系。在之前介紹多線程時(shí)有些過(guò)一篇關(guān)于volatile的文章,有提到主內(nèi)存、線程本地內(nèi)存,當(dāng)時(shí)對(duì)于兩者之間的關(guān)系并沒(méi)有深入介紹,留到介紹內(nèi)存模型時(shí)才來(lái)講解。主內(nèi)存(Main Memory)指的是所有線程可以共享的內(nèi)存區(qū)域,本地內(nèi)存(Local Memory)指的是線程本地私有的內(nèi)存,其存儲(chǔ)的是主內(nèi)存中的一份副本。其實(shí)本地內(nèi)存只是JMM的一個(gè)抽象,在物理上根本是不存在的。

順序一致性模型
順序一致性模型是一種理論化模型,其只是作為Java內(nèi)存模型設(shè)計(jì)的一種參考。其主要包含兩大特性:
1、在一個(gè)線程中的所有操作,必須按照程序的順序來(lái)執(zhí)行;
2、(不管程序是否同步)從每個(gè)線程的角度去看所有的操作都是有序的。在順序一致性模型中所有的操作都具有原子性、而且其結(jié)果對(duì)所有線程可見(jiàn)。
對(duì)于上述第1點(diǎn),大家都比較好理解,對(duì)于單線程來(lái)說(shuō),我們希望程序執(zhí)行順序是有序的。對(duì)于第二點(diǎn),多線程環(huán)境下的有序,大家或許會(huì)感到疑惑,因?yàn)槎嗑€程存在并發(fā)問(wèn)題,下面來(lái)重點(diǎn)分析這一種情況。
假設(shè)在一個(gè)程序中存在2個(gè)線程,線程A的執(zhí)行順序?yàn)椋篈1--->A2--->A3,線程B的執(zhí)行順序?yàn)椋築1--->B2--->B3。若是線程同步的話,A、B線程的執(zhí)行順序?yàn)椋僭O(shè)A線程先執(zhí)行):A1--->A2--->A3--->B1--->B2--->B3,符合上述第2點(diǎn)。若是A、B線程并不同步執(zhí)行的話,情況則不一樣了。假設(shè)其執(zhí)行結(jié)果為下圖:

上圖可以看出,無(wú)論是從線程A還是線程B的角度他們的執(zhí)行順序都沒(méi)有發(fā)生變化,論證了上述第二個(gè)特性,所有的操作都是按照順序執(zhí)行。
happens-before原則
JSR-133內(nèi)存模型使用happens-before原則來(lái)闡述程序執(zhí)行過(guò)程與內(nèi)存可見(jiàn)行。在JMM中,如果一個(gè)操作的執(zhí)行結(jié)果對(duì)另外一個(gè)操作來(lái)說(shuō)是可見(jiàn)的,那么這兩個(gè)操作就必須要遵循h(huán)appens-before規(guī)則。需要注意的是,happens-before描述的是操作結(jié)果的可見(jiàn)行,并不是操作的執(zhí)行順序下面介紹happens-before的具體規(guī)則:
程序順序規(guī)則:一個(gè)線程中的所有操作,happens-before于該線程中的任意后續(xù)操作;
監(jiān)視器鎖規(guī)則:對(duì)于一個(gè)鎖的解鎖操作,happens-before于隨后對(duì)其進(jìn)行加鎖的操作;
volatile變量規(guī)則:對(duì)于一個(gè)volatile變量的寫(xiě)操作,happens-before于任意后續(xù)對(duì)這個(gè)變量的讀操作;
傳遞性規(guī)則:如果A操作 happens-before B操作,且B操作happens-before C操作,那么A操作 happens-before C操作;
重排序介紹
重排序是指編譯器和處理器為了優(yōu)化程序執(zhí)行的性能而進(jìn)行重新排序的一種手段。例如,a=1;b=2;這兩個(gè)操作,就可能會(huì)被編譯器或處理器進(jìn)行重排序,改變其執(zhí)行順序。
數(shù)據(jù)依賴性
數(shù)據(jù)依賴性簡(jiǎn)單來(lái)說(shuō)就是兩個(gè)操作之間對(duì)于數(shù)據(jù)存在依賴,后面的操作依賴于前面的操作。例如,如果兩個(gè)操作同時(shí)訪問(wèn)同一個(gè)變量,且其中一個(gè)操作為變量的寫(xiě)操作,那么這兩個(gè)操作之間就存在數(shù)據(jù)依賴性。下面將舉例說(shuō)明:
寫(xiě)后讀: a=1;b=1;
寫(xiě)后寫(xiě): a=1;a=2;
讀后寫(xiě): a=b;b=1;
上述例子中的兩個(gè)操作之間是存在數(shù)據(jù)依賴性的,因?yàn)橹灰淖儍蓚€(gè)操作執(zhí)行的順序那么其結(jié)果就會(huì)與預(yù)期不同。因此編譯器與處理器在進(jìn)行重排序時(shí),必須要遵守?cái)?shù)據(jù)依賴性。當(dāng)然這里所說(shuō)的數(shù)據(jù)依賴性僅僅針對(duì)的是單線程和單個(gè)處理器的情況,對(duì)于多個(gè)處理器和多線程之間的數(shù)據(jù)依賴性不會(huì)被編譯器和處理器考慮。
as-if-serial語(yǔ)義
as-if-serial的語(yǔ)義是指不論編譯器和處理器如何重排序,對(duì)于單線程來(lái)說(shuō),程序的執(zhí)行結(jié)果都不能被改變。
結(jié)束語(yǔ)
寫(xiě)到這里相信大家對(duì)于Java內(nèi)存模型會(huì)有一些了解,文章重點(diǎn)在于介紹Java內(nèi)存模型中的一些理論知識(shí)。理解重排序,對(duì)于實(shí)際開(kāi)發(fā)工作中關(guān)于多線程并發(fā)同步很有幫助的。對(duì)于多線程來(lái)說(shuō),編譯器、處理器的優(yōu)化不會(huì)考慮其數(shù)據(jù)依賴性,那么就可能改變預(yù)期執(zhí)行結(jié)果,需要程序員自己通過(guò)同步手段進(jìn)行控制。從我個(gè)人經(jīng)驗(yàn)來(lái)看,對(duì)于這些知識(shí)的理解,能讓自己從使用轉(zhuǎn)向明白為什么的階段。
參考書(shū)籍:
《Java并發(fā)編程藝術(shù)》
《深入理解Java虛擬機(jī)》