摘要:內(nèi)存模型對(duì)內(nèi)存模型的介紹對(duì)內(nèi)存模型的結(jié)構(gòu)圖的線程之間的通信是通過(guò)共享內(nèi)存的方式進(jìn)行隱式通信,即線程把某狀態(tài)寫(xiě)入主內(nèi)存中的共享變量,線程讀取的值,這樣就完成了通信。
Java內(nèi)存模型(JMM) 1.對(duì)內(nèi)存模型的介紹 ①對(duì)Java內(nèi)存模型的結(jié)構(gòu)圖
java的線程之間的通信是通過(guò)“共享內(nèi)存”的方式進(jìn)行隱式通信,即線程A把某狀態(tài)寫(xiě)入主內(nèi)存中的共享變量X,線程B讀取X的值,這樣就完成了通信。是一種隱式的通信方式。
一個(gè)線程的模型可以類(lèi)比現(xiàn)在的CPU,一個(gè)CPU會(huì)具備高速緩存,來(lái)緩解CPU速度和內(nèi)存IO速度的巨大差距,線程也是類(lèi)似的,一個(gè)線程擁有其本地內(nèi)存,相當(dāng)于是用來(lái)緩存主內(nèi)存中的值的。
也就是說(shuō),線程并不直接與主內(nèi)存通信,而是線程先把主內(nèi)存中的共享變量備份到私有的本地內(nèi)存中,線程是使用本地內(nèi)存中的值。
虛擬機(jī),甚至硬件本身的優(yōu)化措施,會(huì)優(yōu)先將本地內(nèi)存存儲(chǔ)在寄存器和高速緩存。
②JMM的作用java的最大賣(mài)點(diǎn)是“一次編寫(xiě),到處運(yùn)行”,為了實(shí)現(xiàn)讓java在各種平臺(tái)上都能有一致的內(nèi)存訪問(wèn)效果,java虛擬機(jī)規(guī)范必須定義一種Java內(nèi)存模型來(lái)屏蔽各種硬件和操作系統(tǒng)的內(nèi)存訪問(wèn)差異。
③JMM的特征java內(nèi)存模型是圍繞著如何處理原子性,可見(jiàn)性,有序性來(lái)建立的。
原子性:原子性指多個(gè)操作的組合要么一起執(zhí)行完,要么全部不執(zhí)行,這個(gè)很好理解,一個(gè)線程在執(zhí)行一組操作的中途,不能被另一個(gè)線程插一腳,不然會(huì)造成數(shù)據(jù)錯(cuò)誤,最經(jīng)典就是 a++;操作,++ 操作符不是原子的,所以需要使用同步工具保證其原子性。
可見(jiàn)性:根據(jù)java內(nèi)存模型的結(jié)構(gòu),各個(gè)線程都會(huì)從主內(nèi)存?zhèn)浞菀粋€(gè)變量的工作內(nèi)存放在自己的工作內(nèi)存作為緩存,可以提高效率,這樣就造成了可見(jiàn)性問(wèn)題,即一個(gè)線程修改了一個(gè)數(shù)據(jù),如果一沒(méi)有立即同步回主內(nèi)存,二沒(méi)有讓其他使用這個(gè)數(shù)據(jù)的線程及時(shí)從主內(nèi)存同步,則其他線程的數(shù)據(jù)是錯(cuò)誤的。
有序性:編譯器和處理器為了獲得更高的效率,會(huì)對(duì)指令進(jìn)行重排序,實(shí)際生成的字節(jié)碼指令順序或者處理器指令順序并非是程序源代碼中的順序,這個(gè)在單線程的情況下問(wèn)題不大,因?yàn)榫幾g器和處理器會(huì)保證結(jié)果正確,但是多線程的環(huán)境下,因?yàn)榫€程之間很多時(shí)候需要協(xié)調(diào),如果指令進(jìn)行重排,會(huì)影響協(xié)調(diào)結(jié)果錯(cuò)亂,可以從一個(gè)經(jīng)典的例子來(lái)說(shuō)明,代碼如下:
假設(shè)有兩個(gè)線程A,B ,A線程先執(zhí)行write方法,接下來(lái)B線程執(zhí)行read()方法,write方法中的兩個(gè)操作,并沒(méi)有必要的順序關(guān)系,在實(shí)際執(zhí)行中,編譯器或者處理器有權(quán)利進(jìn)行重排序,先對(duì)flag賦值,然后對(duì)a賦值,巧了,B線程對(duì)flag的讀取正好在A線程兩個(gè)操作的中間,即B線程讀取到了flag為true,但是a卻還是0,造成了數(shù)據(jù)的錯(cuò)誤。。
因此,JMM必須提供了一種機(jī)制來(lái)禁止類(lèi)似的重排序,詳見(jiàn)volatile的內(nèi)存語(yǔ)義,其提供了對(duì)有序性的保證。
class OrderExample{ int a =0 ; boolean flag = false; public void write(){ a = 1; flag = true; } public void read(){ if(flag){ int i = a + 1; } } }④JMM的設(shè)計(jì)要求
我們可以把JMM看做是程序員和平臺(tái)的中間人。程序員和平臺(tái)需要談判卻==不直接交流==,而是通過(guò)JMM來(lái)傳話。
先看雙方的需要:
程序員的需求:程序員希望內(nèi)存模型簡(jiǎn)單易懂,符合人類(lèi)的直覺(jué),所以渴望一個(gè)強(qiáng)內(nèi)存模型
編譯器和處理器的需要:編譯器,處理器希望內(nèi)存模型對(duì)自己的束縛越小越好,這樣就可以做更多的優(yōu)化來(lái)提高執(zhí)行速度,編譯器,處理器渴望弱內(nèi)存模型。
所以JMM有兩個(gè)設(shè)計(jì)需求,1.為程序員提供可見(jiàn)性保證,2.盡可能放松對(duì)編譯器,處理器的限制。
所謂談判,是一個(gè)妥協(xié)的過(guò)程,JMM給了程序員一些“先行發(fā)生原則happens-before”的保證,程序員的代碼中的操作之間關(guān)系只要符合這些規(guī)則,那么平臺(tái)不會(huì)隨意對(duì)這些操作重排序,程序員根據(jù)這個(gè)保證,可以使編程更加容易和健壯,更符合人類(lèi)的直覺(jué)。同時(shí),JMM也放寬了對(duì)平臺(tái)的限制,只要能保證那些“happens-before規(guī)則”,平臺(tái)可以對(duì)操作進(jìn)行重排序。
2.內(nèi)存模型如何實(shí)現(xiàn)三個(gè)特性 ①主內(nèi)存與工作內(nèi)存之間的交互協(xié)議定義了一個(gè)變量如何從主內(nèi)存中拷貝到工作內(nèi)存(本地內(nèi)存),如何從工作內(nèi)存同步回主內(nèi)存的實(shí)現(xiàn)細(xì)節(jié)。
JMM中定義了8中操作來(lái)完成以上工作,每種操作都是原子的,不可再分的(double,long類(lèi)型的變量,load,store,read,write操作在某些平臺(tái)上允許有例外)
命令 | 作用于何處的變量 | 作用描述 |
---|---|---|
lock | 主內(nèi)存 | 把一個(gè)變量標(biāo)識(shí)為一個(gè)線程獨(dú)占的狀態(tài) |
unlock | 主內(nèi)存 | 把一個(gè)變量從鎖定狀態(tài)釋放出來(lái),與lock對(duì)應(yīng) |
read | 主內(nèi)存 | 把一個(gè)變量的值從主內(nèi)存中傳輸到線程的工作內(nèi)存,供load使用 |
load | 工作內(nèi)存 | 把read操作得到的值放入到工作內(nèi)存的變量副本中 |
use | 工作內(nèi)存 | 把工作內(nèi)存中的值傳遞給執(zhí)行引擎 |
assign | 工作內(nèi)存 | 把從執(zhí)行引擎收到的值賦值給工作內(nèi)存中的該變量 |
store | 工作內(nèi)存 | 將工作內(nèi)存中的該變量的值傳送到主內(nèi)存 |
write | 主內(nèi)存 | 將store操作得到的值放入主內(nèi)存的變量中 |
這些操作需要必須遵循的規(guī)定:
JMM規(guī)定read-load,store-write兩對(duì)操作必須順序執(zhí)行,而且必須成對(duì)出現(xiàn),但是不規(guī)定連續(xù)執(zhí)行,
工作內(nèi)存有狀態(tài)的改變必須同步會(huì)主內(nèi)存
不允許沒(méi)有發(fā)生過(guò)任何assign的情況下把數(shù)據(jù)同步回主內(nèi)存
一個(gè)新的變量只能在主內(nèi)存中誕生,即use和store之前必須有對(duì)該變量的assign,load
一個(gè)變量在同一時(shí)刻只允許一個(gè)線程對(duì)其執(zhí)行l(wèi)ock操作,但是允許該線程多次執(zhí)行l(wèi)ock操作,對(duì)應(yīng)的,unlock也必須執(zhí)行相同的次數(shù)才能解鎖。可重入鎖
對(duì)一個(gè)變量執(zhí)行l(wèi)ock操作,必須清空工作內(nèi)存中此變量的值,在執(zhí)行引擎使用該變量之前,重新執(zhí)行l(wèi)oad或assign。 synchronized也具備內(nèi)存可見(jiàn)性
如果一個(gè)變量事先未被Lock鎖定,那么不允許對(duì)其unlock操作,也不能unlock一個(gè)被其他線程鎖定的變量。
對(duì)一個(gè)變量unlock操作之前,必須把此變量同步會(huì)主內(nèi)存。也可服務(wù)于synchronized的可見(jiàn)性
8中內(nèi)存訪問(wèn)以及上述的8個(gè)規(guī)定限制,加上volatile的寫(xiě)特殊規(guī)定,已經(jīng)完全確定了java程序的那些內(nèi)存訪問(wèn)操作是線程安全的。 以上的規(guī)定的一個(gè)等效判斷原則就是 ==happens-before==。 ②三個(gè)特性的實(shí)現(xiàn)
JMM使用read,load,assign,use,store,write來(lái)訪問(wèn)基本數(shù)據(jù),這些操作都是原子的,所以基本可以認(rèn)為JMM對(duì)基本數(shù)據(jù)類(lèi)型的訪問(wèn)是原子的
對(duì)于更大范圍的原子性保證,JMM提供了lock和unlock來(lái)滿足這種需求,lock和unlock并未直接提供給用戶使用,但是可以通過(guò)更高級(jí)的字節(jié)碼指令,monitorenter,monitorexit來(lái)隱式使用。JVM基于進(jìn)入和退出Monitor對(duì)象來(lái)實(shí)現(xiàn)方法同步和代碼塊同步,具體的使用可以看synchronized原語(yǔ)的實(shí)現(xiàn)。
volatile變量提供的可見(jiàn)性:普通變量和volatile變量都是通過(guò)主內(nèi)存作為線程間分享數(shù)據(jù)的渠道,不同的是,volatile變量能及時(shí)同步到主內(nèi)存并且其他線程工作內(nèi)存中該變量的值立即失效,需要重新從主內(nèi)存中加載,普通變量不保證。
synchronized提供的可見(jiàn)性:實(shí)現(xiàn)方式和volatile有所不同,"八大規(guī)定"中說(shuō),unlock操作之前,必須先把變量同步到主內(nèi)存中,而lock之前,必須清空工作內(nèi)存中的值,重新從主內(nèi)存中加載。這兩條保證了synchronized具備內(nèi)存可見(jiàn)性。
final提供的內(nèi)存可見(jiàn)性:final的重排序規(guī)則明確了,只要正確構(gòu)造一個(gè)對(duì)象,那么當(dāng)線程獲得這個(gè)對(duì)象的時(shí)候,其final域已經(jīng)正確完成初始化,對(duì)其他線程可見(jiàn)。
volatile本身禁止指令重排序,
synchronized像是把多線程的環(huán)境變?yōu)榱藛尉€程的環(huán)境,并行變串行,指令重排必須保證串行語(yǔ)義的一致性。
③“天然的”先行發(fā)生原則 happens-beforehappens-before服務(wù)于三大原則中的有序性,Java程序中天然的(未使用volatile或者synchronized)有序性可以總結(jié)為一句話:
如果在本線程觀察,所有操作都是有序的,如果在另一個(gè)線程中觀察,所有操作都是無(wú)序的。
試想一下,如果編碼過(guò)程中所有的操作都要使用volatile或者synchronized來(lái)保證有序性,那么將是多大的負(fù)擔(dān),程序的復(fù)雜性也會(huì)極大上升。所以java中提供了一些天然的先行發(fā)生原則,是指那些無(wú)需任何同步手段就天然具備順序性的先行發(fā)生原則。是8個(gè)內(nèi)存操作的規(guī)則的另一種表達(dá)。
程序次序規(guī)則:在一個(gè)線程內(nèi),按照控制流,前面的操作先于后面的操作,對(duì)后續(xù)操作可見(jiàn)。as-if-serial語(yǔ)義。
管程鎖定規(guī)則:一個(gè)unlock操作必須先行發(fā)生于同一個(gè)鎖的lock操作
volatile變量規(guī)則:對(duì)volatile的寫(xiě)操作必須happens-before后續(xù)對(duì)該變量的讀操作。
線程啟動(dòng)規(guī)則:start()方法happens-before該線程的其他所有動(dòng)作
線程終止規(guī)則:線程中所有的操作happens-before該線程的終止檢測(cè),如isAlive()方法。
線程中斷規(guī)則:interrupt()方法happens-before對(duì)線程中斷的檢測(cè)
對(duì)象終結(jié)規(guī)則:一個(gè)對(duì)象初始化完成happens-before其finilize()方法的開(kāi)始
傳遞性,A hannens-before B,B happens-before C,則A happens-before C.
如果兩個(gè)操作的關(guān)系無(wú)法從上述規(guī)則中推倒出來(lái),則虛擬機(jī)可以對(duì)它們隨意重排序。衡量并發(fā)安全問(wèn)題,應(yīng)該以先行發(fā)生原則為準(zhǔn)。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/69352.html
摘要:因?yàn)楣芾砣藛T是了解手下的人員以及自己負(fù)責(zé)的事情的。處理器優(yōu)化和指令重排上面提到在在和主存之間增加緩存,在多線程場(chǎng)景下會(huì)存在緩存一致性問(wèn)題。有沒(méi)有發(fā)現(xiàn),緩存一致性問(wèn)題其實(shí)就是可見(jiàn)性問(wèn)題。 網(wǎng)上有很多關(guān)于Java內(nèi)存模型的文章,在《深入理解Java虛擬機(jī)》和《Java并發(fā)編程的藝術(shù)》等書(shū)中也都有關(guān)于這個(gè)知識(shí)點(diǎn)的介紹。但是,很多人讀完之后還是搞不清楚,甚至有的人說(shuō)自己更懵了。本文,就來(lái)整體的...
摘要:因?yàn)楣芾砣藛T是了解手下的人員以及自己負(fù)責(zé)的事情的。處理器優(yōu)化和指令重排上面提到在在和主存之間增加緩存,在多線程場(chǎng)景下會(huì)存在緩存一致性問(wèn)題。有沒(méi)有發(fā)現(xiàn),緩存一致性問(wèn)題其實(shí)就是可見(jiàn)性問(wèn)題。 網(wǎng)上有很多關(guān)于Java內(nèi)存模型的文章,在《深入理解Java虛擬機(jī)》和《Java并發(fā)編程的藝術(shù)》等書(shū)中也都有關(guān)于這個(gè)知識(shí)點(diǎn)的介紹。但是,很多人讀完之后還是搞不清楚,甚至有的人說(shuō)自己更懵了。本文,就來(lái)整體的...
摘要:編譯器,和處理器會(huì)共同確保單線程程序的執(zhí)行結(jié)果與該程序在順序一致性模型中的執(zhí)行結(jié)果相同。正確同步的多線程程序的執(zhí)行將具有順序一致性程序的執(zhí)行結(jié)果與該程序在順序一致性內(nèi)存模型中的執(zhí)行結(jié)果相同。 前情提要 深入理解Java內(nèi)存模型(六)——final 處理器內(nèi)存模型 順序一致性內(nèi)存模型是一個(gè)理論參考模型,JMM和處理器內(nèi)存模型在設(shè)計(jì)時(shí)通常會(huì)把順序一致性內(nèi)存模型作為參照。JMM和處理器內(nèi)...
摘要:內(nèi)存模型即,簡(jiǎn)稱,其規(guī)范了虛擬機(jī)與計(jì)算機(jī)內(nèi)存時(shí)如何協(xié)同工作的,規(guī)定了一個(gè)線程如何和何時(shí)看到其他線程修改過(guò)的值,以及在必須時(shí),如何同步訪問(wèn)共享變量。內(nèi)存模型要求調(diào)用棧和本地變量存放在線程棧上,對(duì)象存放在堆上。 Java內(nèi)存模型即Java Memory Model,簡(jiǎn)稱JMM,其規(guī)范了Java虛擬機(jī)與計(jì)算機(jī)內(nèi)存時(shí)如何協(xié)同工作的,規(guī)定了一個(gè)線程如何和何時(shí)看到其他線程修改過(guò)的值,以及在必須時(shí),...
摘要:作為一個(gè)程序員,不了解內(nèi)存模型就不能寫(xiě)出能夠充分利用內(nèi)存的代碼。程序計(jì)數(shù)器是在電腦處理器中的一個(gè)寄存器,用來(lái)指示電腦下一步要運(yùn)行的指令序列。在虛擬機(jī)中,本地方法棧和虛擬機(jī)棧是共用同一塊內(nèi)存的,不做具體區(qū)分。 作為一個(gè) Java 程序員,不了解 Java 內(nèi)存模型就不能寫(xiě)出能夠充分利用內(nèi)存的代碼。本文通過(guò)對(duì) Java 內(nèi)存模型的介紹,讓讀者能夠了解 Java 的內(nèi)存的分配情況,適合 Ja...
閱讀 2764·2021-11-08 13:16
閱讀 2430·2021-10-18 13:30
閱讀 2333·2021-09-27 13:35
閱讀 2066·2019-08-30 15:55
閱讀 2497·2019-08-30 13:22
閱讀 645·2019-08-30 11:24
閱讀 2142·2019-08-29 12:33
閱讀 1879·2019-08-26 12:10