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