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