java鎖(1)java內(nèi)存模型

1、內(nèi)存模型基礎(chǔ)

在并發(fā)編程中,需要處理兩個關(guān)鍵問題:線程之間如何通信及線程之間如何同步。通信是指線程之間以何種機制來交換信息。在命令式編程中,線程之間的通信機制有兩種:共享內(nèi)存和消息傳遞。

在共享內(nèi)存的并發(fā)模型里,線程之間共享程序的公共狀態(tài),通過寫-讀內(nèi)存中的公共狀態(tài)進行隱式通信。在消息傳遞的并發(fā)模型里,線程之間沒有公共狀態(tài),線程之間必須通過發(fā)送消息來顯式進行通信。

同步是指程序中用于控制不同線程間操作發(fā)生相對順序的機制。在共享內(nèi)存并發(fā)模型里,同步是顯式進行的。程序員必須顯式指定某個方法或某段代碼需要在線程之間互斥執(zhí)行。在消息傳遞的并發(fā)模型里,由于消息的發(fā)送必須在消息的接收之前,因此同步是隱式進行的。

Java的并發(fā)采用的是共享內(nèi)存模型,Java線程之間的通信總是隱式進行,整個通信過程對程序員完全透明。如果編寫多線程程序的Java程序員不理解隱式進行的線程之間通信的工作機制,很可能會遇到各種奇怪的內(nèi)存可見性問題。

1.1、內(nèi)存模型抽象結(jié)構(gòu)

在Java中,所有實例域、靜態(tài)域和數(shù)組元素都存儲在堆內(nèi)存中,堆內(nèi)存在線程之間共享(本章用“共享變量”這個術(shù)語代指實例域,靜態(tài)域和數(shù)組元素)。局部變量(Local Variables),方法定義參數(shù)(Java語言規(guī)范稱之為Formal Method Parameters)和異常處理器參數(shù)(Exception Handler Parameters)不會在線程之間共享,它們不會有內(nèi)存可見性問題,也不受內(nèi)存模型的影響。

從抽象的角度來看,JMM定義了線程和主內(nèi)存之間的抽象關(guān)系:線程之間的共享變量存儲在主內(nèi)存(Main Memory)中,每個線程都有一個私有的本地內(nèi)存(Local Memory),本地內(nèi)存中存儲了該線程以讀/寫共享變量的副本。本地內(nèi)存是JMM的一個抽象概念,并不真實存在。它涵蓋了緩存、寫緩沖區(qū)、寄存器以及其他的硬件和編譯器優(yōu)化。

內(nèi)存模式.png

如果線程A與線程B之間要通信的話,必須要經(jīng)歷下面2個步驟:

  • 線程A把本地內(nèi)存A中更新過的共享變量刷新到主內(nèi)存中去。
  • 線程B到主內(nèi)存中去讀取線程A之前已更新過的共享變量。

從整體來看,這兩個步驟實質(zhì)上是線程A在向線程B發(fā)送消息,而且這個通信過程必須要經(jīng)過主內(nèi)存。JMM通過控制主內(nèi)存與每個線程的本地內(nèi)存之間的交互,來為Java程序員提供內(nèi)存可見性保證。

2、重排序

在執(zhí)行程序時,為了提高性能,編譯器和處理器常常會對指令做重排序。重排序分3種類型。

  • 編譯器優(yōu)化的重排序。編譯器在不改變單線程程序語義的前提下,可以重新安排語句的執(zhí)行順序。

  • 指令級并行的重排序?,F(xiàn)代處理器采用了指令級并行技術(shù)(Instruction-LevelParallelism,ILP)來將多條指令重疊執(zhí)行。如果不存在數(shù)據(jù)依賴性,處理器可以改變語句對應(yīng)機器指令的執(zhí)行順序。

  • 內(nèi)存系統(tǒng)的重排序。由于處理器使用緩存和讀/寫緩沖區(qū),這使得加載和存儲操作看上去可能是在亂序執(zhí)行。

從Java源代碼到最終實際執(zhí)行的指令序列,會分別經(jīng)歷下面3種重排序:

指令優(yōu)化步驟.png

這些重排序可能會導(dǎo)致多線程程序出現(xiàn)內(nèi)存可見性問題。對于編譯器,JMM的編譯器重排序規(guī)則會禁止特定類型的編譯器重排序(不是所有的編譯器重排序都要禁止)。對于處理器重排序,JMM的處理器重排序規(guī)則會要求Java編譯器在生成指令序列時,插入特定類型的內(nèi)存屏障(Memory Barriers,Intel稱之為Memory Fence)指令,通過內(nèi)存屏障指令來禁止特定類型的處理器重排序。

JMM屬于語言級的內(nèi)存模型,它確保在不同的編譯器和不同的處理器平臺之上,通過禁止特定類型的編譯器重排序和處理器重排序,為程序員提供一致的內(nèi)存可見性保證。

as-if-serial語義的意思是:不管怎么重排序(編譯器和處理器為了提高并行度),(單線程)

程序的執(zhí)行結(jié)果不能被改變。編譯器、runtime和處理器都必須遵守as-if-serial語義。

為了遵守as-if-serial語義,編譯器和處理器不會對存在數(shù)據(jù)依賴關(guān)系的操作做重排序,因為這種重排序會改變執(zhí)行結(jié)果。但是,如果操作之間不存在數(shù)據(jù)依賴關(guān)系,這些操作就可能被編譯器和處理器重排序。

as-if-serial語義把單線程程序保護了起來,遵守as-if-serial語義的編譯器、runtime和處理器共同為編寫單線程程序的程序員創(chuàng)建了一個幻覺:單線程程序是按程序的順序來執(zhí)行的。asif-serial語義使單線程程序員無需擔(dān)心重排序會干擾他們,也無需擔(dān)心內(nèi)存可見性問題。

3、順序一致性

JMM對正確同步的多線程程序的內(nèi)存一致性做了如下保證:

如果程序是正確同步的,程序的執(zhí)行將具有順序一致性(Sequentially Consistent)——即程序的執(zhí)行結(jié)果與該程序在順序一致性內(nèi)存模型中的執(zhí)行結(jié)果相同。馬上我們就會看到,這對于程序員來說是一個極強的保證。這里的同步是指廣義上的同步,包括對常用同步原語(synchronized、volatile和final)的正確使用。

3.1、順序一致性內(nèi)存模型

順序一致性內(nèi)存模型是一個被計算機科學(xué)家理想化了的理論參考模型,它為程序員提供了極強的內(nèi)存可見性保證。順序一致性內(nèi)存模型有兩大特性:

  • 一個線程中的所有操作必須按照程序的順序來執(zhí)行。
  • (不管程序是否同步)所有線程都只能看到一個單一的操作執(zhí)行順序。在順序一致性內(nèi)存模型中,每個操作都必須原子執(zhí)行且立刻對所有線程可見。

順序一致性內(nèi)存模型為程序員提供的視圖:

順序一致性內(nèi)存模型.png

在概念上,順序一致性模型有一個單一的全局內(nèi)存,這個內(nèi)存通過一個左右擺動的開關(guān)可以連接到任意一個線程,同時每一個線程必須按照程序的順序來執(zhí)行內(nèi)存讀/寫操作。從上面的示意圖可以看出,在任意時間點最多只能有一個線程可以連接到內(nèi)存。當(dāng)多個線程并發(fā)執(zhí)行時,圖中的開關(guān)裝置能把所有線程的所有內(nèi)存讀/寫操作串行化(即在順序一致性模型中,所有操作之間具有全序關(guān)系)。

未同步程序在順序一致性模型中雖然整體執(zhí)行順序是無序的,但所有線程都只能看到一個一致的整體執(zhí)行順序。但是,在JMM中就沒有這個保證。未同步程序在JMM中不但整體的執(zhí)行順序是無序的,而且所有線程看到的操作執(zhí)行順序也可能不一致。比如,在當(dāng)前線程把寫過的數(shù)據(jù)緩存在本地內(nèi)存中,在沒有刷新到主內(nèi)存之前,這個寫操作僅對當(dāng)前線程可見;從其他線程的角度來觀察,會認為這個寫操作根本沒有被當(dāng)前線程執(zhí)行。只有當(dāng)前線程把本地內(nèi)存中寫過的數(shù)據(jù)刷新到主內(nèi)存之后,這個寫操作才能對其他線程可見。在這種情況下,當(dāng)前線程和其他線程看到的操作執(zhí)行順序?qū)⒉灰恢隆?/p>

3.2、同步程序的順序一致性效果

還有 66% 的精彩內(nèi)容
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
支付 ¥2.99 繼續(xù)閱讀

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容