這篇文章主要介紹“Java內(nèi)存模型的工作模式是什么”,在日常操作中,相信很多人在Java內(nèi)存模型的工作模式是什么問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Java內(nèi)存模型的工作模式是什么”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!
目前成都創(chuàng)新互聯(lián)已為1000多家的企業(yè)提供了網(wǎng)站建設(shè)、域名、雅安服務(wù)器托管、綿陽服務(wù)器托管、企業(yè)網(wǎng)站設(shè)計、柴桑網(wǎng)站維護等服務(wù),公司將堅持客戶導(dǎo)向、應(yīng)用為本的策略,正道將秉承"和諧、參與、激情"的文化,與客戶和合作伙伴齊心協(xié)力一起成長,共同發(fā)展。
現(xiàn)代計算機中,CPU的指令速度遠超內(nèi)存的存取速度,由于CPU和內(nèi)存的運算速度有幾個數(shù)量級的差距,所以現(xiàn)代計算機系統(tǒng)加入一層讀寫速度盡可能接近CPU運算速度的高速緩存(Cache)來作為內(nèi)存與處理器之間的緩沖,將運算需要使用到的數(shù)據(jù)復(fù)制到緩存中,CPU運算操作的是內(nèi)存數(shù)據(jù)的副本,當(dāng)運算結(jié)束后再從緩存將副本數(shù)據(jù)同步回內(nèi)存之中,這樣處理器就無須等待緩慢的內(nèi)存讀寫了。
當(dāng)CPU要讀取一個數(shù)據(jù)時,首先從一級緩存中查找,如果沒有找到再從二級緩存中查找,如果還是沒有就從三級緩存或內(nèi)存中查找。
計算機在運行中,先從內(nèi)存中取出第一條指令,通過控制器譯碼,按照指令的要求從存儲器中取出數(shù)據(jù)進行制定的邏輯運算后再按照內(nèi)存地址把結(jié)果寫回內(nèi)存中,接著再取出來第二條指令,依次遍歷執(zhí)行下去。
單線程:CPU核心緩存只被一個線程訪問。緩存獨占,不會出現(xiàn)訪問沖突問題。
單核 CPU,多線程:進程中的多個線程會同時訪問進程中的共享數(shù)據(jù),CPU 將某塊內(nèi)存加載到緩存后,不同線程在訪問相同的物理地址的時候,都會映射到相同的緩存位置,這樣即使發(fā)生線程的切換,緩存仍然不會失效,何時候只有一個線程在執(zhí)行,不會發(fā)生訪問沖突的問題。
多核 CPU,多線程:每個核都至少有一個 L1 緩存。多個線程訪問進程中的某個共享內(nèi)存,且這多個線程分別在不同的核心上執(zhí)行,則每個核心都會在各自的Cache中保留一份共享內(nèi)存的緩沖,由于多核是可以并行的,可能會出現(xiàn)多個線程同時寫各自的緩存的情況,而各自的 Cache 之間的數(shù)據(jù)就有不一致性的問題(并發(fā)導(dǎo)致的根源問題)。
為了解決這個問題,需要各個處理器訪問緩存時都遵循一些協(xié)議,在讀寫時要根據(jù)協(xié)議來進行操作,這個協(xié)議就是MESI協(xié)議(緩存一致性協(xié)議)
在不同的硬件生產(chǎn)商和不同的操作系統(tǒng)下,內(nèi)存的訪問邏輯有一定的差異,結(jié)果就是當(dāng)你的代碼在某個系統(tǒng)環(huán)境下運行良好,并且線程安全,但是換了個系統(tǒng)就出現(xiàn)各種問題;
這是因為不同的處理器,在處理器優(yōu)化和指令重排等方面存在差異,造成同樣的代碼,在經(jīng)過不同的處理器優(yōu)化和指令重排后,最后執(zhí)行出來的結(jié)果可能不同,這是我們所不能接受的。
JMM就應(yīng)運而生了,究其根本就是為了解決在并發(fā)環(huán)境下,保證數(shù)據(jù)的安全,滿足場景的可見性、原子性、有序性。
狀態(tài) | 描述 | 監(jiān)聽任務(wù) |
---|---|---|
M 修改 (Modified) | 該Cache line有效,數(shù)據(jù)被修改了,和內(nèi)存中的數(shù)據(jù)不一致,數(shù)據(jù)只存在于本Cache中。 | 緩存行必須時刻監(jiān)聽所有試圖讀該緩存行相對就主存的操作,這種操作必須在緩存將該緩存行寫回主存并將狀態(tài)變成S(共享)狀態(tài)之前被延遲執(zhí)行。 |
E 獨享(Exclusive) | 該Cache line有效,數(shù)據(jù)和內(nèi)存中的數(shù)據(jù)一致,數(shù)據(jù)只存在于本Cache中。 | 緩存行也必須監(jiān)聽其它緩存讀主存中該緩存行的操作,一旦有這種操作,該緩存行需要變成S(共享)狀態(tài)。 |
S 共享 (Shared) | 該Cache line有效,數(shù)據(jù)和內(nèi)存中的數(shù)據(jù)一致,數(shù)據(jù)存在于很多Cache中。 | 緩存行也必須監(jiān)聽其它緩存使該緩存行無效或者獨享該緩存行的請求,并將該緩存行變成無效(Invalid)。 |
I 無效 (Invalid) | 該Cache line無效。 | 無 |
原子性:把一個操作或者多個操作視為一個整體,在執(zhí)行的過程不能被中斷的特性叫原子性。
為了最大化的利用CPU,CPU采用時間片的方式,切換線程執(zhí)行。而在切換線程的過程中會導(dǎo)致原子性問題
進程和線程的本質(zhì)是增加并行/并發(fā)的任務(wù)數(shù)量來提高CPU的執(zhí)行效率,緩存的本質(zhì)是通過減少IO時間來提升CPU的利用率。
CPU指令優(yōu)化的初衷就是想通過調(diào)整CPU指令的執(zhí)行順序或異步化的操作來提升CPU執(zhí)行指令的效率。
為了使得處理器內(nèi)部的運算單元能盡量被充分利用,處理器可能會對輸入代碼進行亂序執(zhí)行優(yōu)化,處理器會在計算之后將亂序執(zhí)行的結(jié)果重組,優(yōu)化原則是保證重排序后不影響單線程執(zhí)行結(jié)果(As If Serial)。
重排序的大體邏輯就是優(yōu)先把CPU比較耗時的指令放到最先執(zhí)行,然后在這些指令執(zhí)行的空余時間來執(zhí)行其他指令。**Java虛擬機的即時編譯器中也有類似的指令重排序優(yōu)化。
(編譯操作-(語義優(yōu)化)指令重排->處理器級別重排(指令優(yōu)化)->內(nèi)存玉華指令重排(內(nèi)存分析優(yōu)化))
為了保證共享內(nèi)存使用的正確性(原子性、有序性、可見性),內(nèi)存模型定義了共享內(nèi)存中多線程讀寫操作的規(guī)范。通過這些規(guī)則來規(guī)范對主內(nèi)存的讀寫,從而保障指令的正確執(zhí)行。
解決了CPU多級緩存、處理器優(yōu)化、指令重排等導(dǎo)致的內(nèi)存訪問問題,保證了并發(fā)場景下的一致性、原子性和有序性。
JMM是一種規(guī)范,目的是解決由于多線程通過共享內(nèi)存進行通信時,存在的工作內(nèi)存數(shù)據(jù)不一致、編譯器對代碼指令重排序、處理器對代碼亂序執(zhí)行、CPU切換線程等帶來的問題。
JMM從java 5開始的JSR-133發(fā)布后,就比較成熟完善了;
JMM是符合內(nèi)存模型規(guī)范的,屏蔽了各種硬件和操作系統(tǒng)的訪問差異的,保證了Java程序在各種平臺下對內(nèi)存的訪問都能保證效果一致的機制。(如同JVM一樣標(biāo)準規(guī)范)。
Java線程內(nèi)存模型規(guī)定:
所有的變量都存儲在主內(nèi)存中,每條線程還有自己的工作內(nèi)存,線程的工作內(nèi)存中保存了該線程中是用到的變量的主內(nèi)存副本拷貝,線程對變量的所有操作都必須在工作內(nèi)存中進行,而不能直接讀寫主內(nèi)存。
不同的線程之間也無法直接訪問對方工作內(nèi)存中的變量,線程間變量的傳遞均需要自己的工作內(nèi)存和主存之間進行數(shù)據(jù)同步進行,也規(guī)定了如何做數(shù)據(jù)同步以及什么時候做數(shù)據(jù)同步。
內(nèi)存交互操作有8種,虛擬機實現(xiàn)必須保證每一個操作都是原子的,不可在分的:
lock(鎖定):作用于主內(nèi)存的變量,把一個變量標(biāo)識為線程獨占狀態(tài)
unlock(解鎖):作用于主內(nèi)存的變量,它把一個處于鎖定狀態(tài)的變量釋放出來,釋放后的變量才可以被其他線程鎖定
read(讀取):作用于主內(nèi)存變量,它把一個變量的值從主內(nèi)存?zhèn)鬏數(shù)骄€程的工作內(nèi)存中,以便隨后的load動作使用
load(載入):作用于工作內(nèi)存的變量,它把read操作從主存中變量放入工作內(nèi)存中;
use(使用):作用于工作內(nèi)存中的變量,它把工作內(nèi)存中的變量傳輸給執(zhí)行引擎,每當(dāng)虛擬機遇到一個需要使用到變量的值,就會使用到這個指令
assign(賦值):作用于工作內(nèi)存中的變量,它把一個從執(zhí)行引擎中接受到的值放入工作內(nèi)存的變量副本中
store(存儲):作用于主內(nèi)存中的變量,它把一個從工作內(nèi)存中一個變量的值傳送到主內(nèi)存中,以便后續(xù)的write使用
write(寫入):作用于主內(nèi)存中的變量,它把store操作從工作內(nèi)存中得到的變量的值放入主內(nèi)存的變量中
不允許read和load、store和write操作之一單獨出現(xiàn),即使用了read必須load,使用了store必須write
不允許線程丟棄他最近的assign操作,即工作變量的數(shù)據(jù)改變了之后,必須告知主存,不允許一個線程將沒有assign的數(shù)據(jù)從工作內(nèi)存同步回主內(nèi)存。
一個新的變量必須在主內(nèi)存中誕生,不允許工作內(nèi)存直接使用一個未被初始化的變量。就是對變量實施use、store操作之前,必須經(jīng)過load、assign操作。
一個變量同一時間只有一個線程能對其進行l(wèi)ock。多次lock后,必須執(zhí)行相同次數(shù)的unlock才能解鎖。
如果對一個變量進行l(wèi)ock操作,會清空所有工作內(nèi)存中此變量的值,在執(zhí)行引擎使用這個變量前,必須重新load或assign操作初始化變量的值。
如果一個變量沒有被lock,就不能對其進行unlock操作。也不能unlock一個被其他線程鎖住的變量。
對一個變量進行unlock操作之前,必須把此變量同步回主內(nèi)存。
是指當(dāng)多個線程訪問同一個變量時,一個線程修改了這個變量的值,其他線程能夠立即看得到修改的值;
Java可見性,可以使用 volatile 關(guān)鍵字,那就是被其修飾的變量在被修改后會立即同步到主內(nèi)存(其實也是要反應(yīng)時間的)主要靠內(nèi)存屏障;
Java 中的 Synchronized 和 Final 兩個關(guān)鍵字也可以實現(xiàn)可見性;
是指在一個操作中被當(dāng)作一個整體,要么一起執(zhí)行,要么就不執(zhí)行;
為了保證原子性,Java提供了兩個高級的字節(jié)碼指令 Monitorenter和 Monitorexit,對應(yīng)的關(guān)鍵字就是 Synchronized;
程序執(zhí)行的順序按照代碼的先后順序執(zhí)行;
可以使用 synchronized 和 volatile 來保證多線程之間操作的有序性,只是兩者實現(xiàn)的方式不一樣,volatile 關(guān)鍵字會禁止指令重排,synchronized 關(guān)鍵字保證同一時刻只允許一條線程操作;
到這里,我們似乎發(fā)現(xiàn)了,Synchronized 好像可以同時滿足三種特征,這也是Synchronized 被用的很頻繁的原因,但是 Synchronized 是比較影響性能的,雖然編譯器提供了很多鎖優(yōu)化技術(shù),但是也不建議過度使用。
分析一個并發(fā)程序是否安全,更多時候其實都依賴Happen-Before原則進行分析。
就是當(dāng)A操作先行發(fā)生于B操作,則在發(fā)生B操作的時候,操作A產(chǎn)生的影響能被B觀察到,“影響”包括修改了內(nèi)存中的共享變量的值、發(fā)送了消息、調(diào)用了方法等;
程序次序規(guī)則(Program Order Rule):在一個線程內(nèi),程序的執(zhí)行規(guī)則跟程序的書寫規(guī)則是一致的,從上往下執(zhí)行。
管程鎖定規(guī)則(Monitor Lock Rule):一個Unlock的操作肯定先于下一次Lock的操作。這里必須是同一個鎖。同理我們可以認為在synchronized同步同一個鎖的時候,鎖內(nèi)先行執(zhí)行的代碼,對后續(xù)同步該鎖的線程來說是完全可見的。
volatile變量規(guī)則(volatile Variable Rule):對同一個volatile的變量,先行發(fā)生的寫操作,肯定早于后續(xù)發(fā)生的讀操作
線程啟動規(guī)則(Thread Start Rule):Thread對象的start()方法先行發(fā)生于此線程的沒一個動作
線程中止規(guī)則(Thread Termination Rule):Thread對象的中止檢測(如:Thread.join(),Thread.isAlive()等)操作,必行晚于線程中所有操作
線程中斷規(guī)則(Thread Interruption Rule):對線程的interruption()調(diào)用,先于被調(diào)用的線程檢測中斷事件(Thread.interrupted())的發(fā)生
對象中止規(guī)則(Finalizer Rule):一個對象的初始化方法先于一個方法執(zhí)行Finalizer()方法
傳遞性(Transitivity):如果操作A先于操作B、操作B先于操作C,則操作A先于操作C
到此,關(guān)于“Java內(nèi)存模型的工作模式是什么”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識,請繼續(xù)關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>
新聞標(biāo)題:Java內(nèi)存模型的工作模式是什么
本文網(wǎng)址:http://weahome.cn/article/pgicie.html