欧美一区二区三区老妇人-欧美做爰猛烈大尺度电-99久久夜色精品国产亚洲a-亚洲福利视频一区二区

深入理解 Java 內(nèi)存模型

2021-03-05    分類: 網(wǎng)站建設(shè)

前提

《深入理解 Java 內(nèi)存模型》程曉明著,該書(shū)在以前看過(guò)一遍,現(xiàn)在學(xué)的東西越多,感覺(jué)那塊越重要,于是又再細(xì)看一遍,于是便有了下面的讀書(shū)筆記總結(jié)。全書(shū)頁(yè)數(shù)雖不多,內(nèi)容講得挺深的。細(xì)看的話,也是挺花時(shí)間的,看完收獲絕對(duì)挺大的。也 基礎(chǔ)

并發(fā)編程的模型分類

在并發(fā)編程需要處理的兩個(gè)關(guān)鍵問(wèn)題是:線程之間如何通信和線程之間如何同步。


通信

通信是指線程之間以何種機(jī)制來(lái)交換信息。在命令式編程中,線程之間的通信機(jī)制有兩種:共享內(nèi)存和消息傳遞。

在共享內(nèi)存的并發(fā)模型里,線程之間共享程序的公共狀態(tài),線程之間通過(guò)寫(xiě)-讀內(nèi)存中的公共狀態(tài)來(lái)隱式進(jìn)行通信。

在消息傳遞的并發(fā)模型里,線程之間沒(méi)有公共狀態(tài),線程之間必須通過(guò)明確的發(fā)送消息來(lái)顯式進(jìn)行通信。


同步

同步是指程序用于控制不同線程之間操作發(fā)生相對(duì)順序的機(jī)制。

在共享內(nèi)存的并發(fā)模型里,同步是顯式進(jìn)行的。程序員必須顯式指定某個(gè)方法或某段代碼需要在線程之間互斥執(zhí)行。

在消息傳遞的并發(fā)模型里,由于消息的發(fā)送必須在消息的接收之前,因此同步是隱式進(jìn)行的。

Java 的并發(fā)采用的是共享內(nèi)存模型,Java 線程之間的通信總是隱式進(jìn)行,整個(gè)通信過(guò)程對(duì)程序員完全透明。


Java 內(nèi)存模型的抽象

在 Java 中,所有實(shí)例域、靜態(tài)域 和 數(shù)組元素存儲(chǔ)在堆內(nèi)存中,堆內(nèi)存在線程之間共享。局部變量、方法定義參數(shù) 和 異常處理器參數(shù) 不會(huì)在線程之間共享,它們不會(huì)有內(nèi)存可見(jiàn)性問(wèn)題,也不受內(nèi)存模型的影響。

Java 線程之間的通信由 Java 內(nèi)存模型(JMM)控制。JMM 決定了一個(gè)線程對(duì)共享變量的寫(xiě)入何時(shí)對(duì)另一個(gè)線程可見(jiàn)。從抽象的角度來(lái)看,JMM 定義了線程與主內(nèi)存之間的抽象關(guān)系:線程之間的共享變量存儲(chǔ)在主內(nèi)存中,每一個(gè)線程都有一個(gè)自己私有的本地內(nèi)存,本地內(nèi)存中存儲(chǔ)了該變量以讀/寫(xiě)共享變量的副本。本地內(nèi)存是 JMM 的一個(gè)抽象概念,并不真實(shí)存在。

JMM 抽象示意圖:

從上圖來(lái)看,如果線程 A 和線程 B 要通信的話,要如下兩個(gè)步驟:

1、線程 A 需要將本地內(nèi)存 A 中的共享變量副本刷新到主內(nèi)存去

2、線程 B 去主內(nèi)存讀取線程 A 之前已更新過(guò)的共享變量

步驟示意圖:

舉個(gè)例子:

本地內(nèi)存 A 和 B 有主內(nèi)存共享變量 X 的副本。假設(shè)一開(kāi)始時(shí),這三個(gè)內(nèi)存中 X 的值都是 0。線程 A 正執(zhí)行時(shí),把更新后的 X 值(假設(shè)為 1)臨時(shí)存放在自己的本地內(nèi)存 A 中。當(dāng)線程 A 和 B 需要通信時(shí),線程 A 首先會(huì)把自己本地內(nèi)存 A 中修改后的 X 值刷新到主內(nèi)存去,此時(shí)主內(nèi)存中的 X 值變?yōu)榱?1。隨后,線程 B 到主內(nèi)存中讀取線程 A 更新后的共享變量 X 的值,此時(shí)線程 B 的本地內(nèi)存的 X 值也變成了 1。

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


重排序

在執(zhí)行程序時(shí)為了提高性能,編譯器和處理器常常會(huì)對(duì)指令做重排序。重排序分三類:

1、編譯器優(yōu)化的重排序。編譯器在不改變指令級(jí)并行的重排序?,F(xiàn)代處理器采用了指令級(jí)并行技術(shù)來(lái)將多條指令重疊執(zhí)行。如果不存在數(shù)據(jù)依賴性,處理器可以改變語(yǔ)句對(duì)應(yīng)機(jī)器指令的執(zhí)行順序。

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

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

上面的這些重排序都可能導(dǎo)致多線程程序出現(xiàn)內(nèi)存可見(jiàn)性問(wèn)題。對(duì)于編譯器,JMM 的編譯器重排序規(guī)則會(huì)禁止特定類型的編譯器重排序(不是所有的編譯器重排序都要禁止)。對(duì)于處理器重排序,JMM 的處理器重排序規(guī)則會(huì)要求 Java 編譯器在生成指令序列時(shí),插入特定類型的內(nèi)存屏障指令,通過(guò)內(nèi)存屏障指令來(lái)禁止特定類型的處理器重排序(不是所有的處理器重排序都要禁止)。

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


處理器重排序

現(xiàn)代的處理器使用寫(xiě)緩沖區(qū)來(lái)臨時(shí)保存向內(nèi)存寫(xiě)入的數(shù)據(jù)。寫(xiě)緩沖區(qū)可以保證指令流水線持續(xù)運(yùn)行,它可以避免由于處理器停頓下來(lái)等待向內(nèi)存寫(xiě)入數(shù)據(jù)而產(chǎn)生的延遲。同時(shí),通過(guò)以批處理的方式刷新寫(xiě)緩沖區(qū),以及合并寫(xiě)緩沖區(qū)中對(duì)同一內(nèi)存地址的多次寫(xiě),可以減少對(duì)內(nèi)存總線的占用。雖然寫(xiě)緩沖區(qū)有這么多好處,但每個(gè)處理器上的寫(xiě)緩沖區(qū),僅僅對(duì)它所在的處理器可見(jiàn)。這個(gè)特性會(huì)對(duì)內(nèi)存操作的執(zhí)行順序產(chǎn)生重要的影響:處理器對(duì)內(nèi)存的讀/寫(xiě)操作的執(zhí)行順序,不一定與內(nèi)存實(shí)際發(fā)生的讀/寫(xiě)操作順序一致!

舉個(gè)例子:

假設(shè)處理器A和處理器B按程序的順序并行執(zhí)行內(nèi)存訪問(wèn),最終卻可能得到 x = y = 0。具體的原因如下圖所示:

處理器 A 和 B 同時(shí)把共享變量寫(xiě)入在寫(xiě)緩沖區(qū)中(A1、B1),然后再?gòu)膬?nèi)存中讀取另一個(gè)共享變量(A2、B2),最后才把自己寫(xiě)緩沖區(qū)中保存的臟數(shù)據(jù)刷新到內(nèi)存中(A3、B3)。當(dāng)以這種時(shí)序執(zhí)行時(shí),程序就可以得到 x = y = 0 的結(jié)果。

從內(nèi)存操作實(shí)際發(fā)生的順序來(lái)看,直到處理器 A 執(zhí)行 A3 來(lái)刷新自己的寫(xiě)緩存區(qū),寫(xiě)操作 A1 才算真正執(zhí)行了。雖然處理器 A 執(zhí)行內(nèi)存操作的順序?yàn)椋篈1 -> A2,但內(nèi)存操作實(shí)際發(fā)生的順序卻是:A2 -> A1。此時(shí),處理器 A 的內(nèi)存操作順序被重排序了。

這里的關(guān)鍵是,由于寫(xiě)緩沖區(qū)僅對(duì)自己的處理器可見(jiàn),它會(huì)導(dǎo)致處理器執(zhí)行內(nèi)存操作的順序可能會(huì)與內(nèi)存實(shí)際的操作執(zhí)行順序不一致。由于現(xiàn)代的處理器都會(huì)使用寫(xiě)緩沖區(qū),因此現(xiàn)代的處理器都會(huì)允許對(duì)寫(xiě)-讀操作重排序。


內(nèi)存屏障指令

為了保證內(nèi)存可見(jiàn)性,Java 編譯器在生成指令序列的適當(dāng)位置會(huì)插入內(nèi)存屏障指令來(lái)禁止特定類型的處理器重排序。JMM 把內(nèi)存屏障指令分為下列四類:


h一個(gè)操作執(zhí)行的結(jié)果需要對(duì)另一個(gè)操作可見(jiàn),那么這兩個(gè)操作之間必須要存在 h
  • 程序順序規(guī)則:一個(gè)線程中的每個(gè)操作,h 數(shù)據(jù)依賴性

    如果兩個(gè)操作訪問(wèn)同一個(gè)變量,且這兩個(gè)操作中有一個(gè)為寫(xiě)操作,此時(shí)這兩個(gè)操作之間就存在數(shù)據(jù)依賴性。數(shù)據(jù)依賴分下列三種類型:

    名稱 代碼示例 說(shuō)明
    寫(xiě)后讀 a = 1; b = a; 寫(xiě)一個(gè)變量之后,再讀這個(gè)位置。
    寫(xiě)后寫(xiě) a = 1; a = 2; 寫(xiě)一個(gè)變量之后,再寫(xiě)這個(gè)變量。
    讀后寫(xiě) a = b; b = 1; 讀一個(gè)變量之后,再寫(xiě)這個(gè)變量。

    上面三種情況,只要重排序兩個(gè)操作的執(zhí)行順序,程序的執(zhí)行結(jié)果將會(huì)被改變。

    前面提到過(guò),編譯器和處理器可能會(huì)對(duì)操作做重排序。編譯器和處理器在重排序時(shí),會(huì)遵守?cái)?shù)據(jù)依賴性,編譯器和處理器不會(huì)改變存在數(shù)據(jù)依賴關(guān)系的兩個(gè)操作的執(zhí)行順序。

    注意,這里所說(shuō)的數(shù)據(jù)依賴性僅針對(duì)單個(gè)處理器中執(zhí)行的指令序列和單個(gè)線程中執(zhí)行的操作,不同處理器之間和不同線程之間的數(shù)據(jù)依賴性不被編譯器和處理器考慮。


    as-if-serial 語(yǔ)義

    as-if-serial 語(yǔ)義的意思指:不管怎么重排序(編譯器和處理器為了提高并行度),(程序的執(zhí)行結(jié)果不能被改變。編譯器,runtime 和處理器都必須遵守 as-if-serial 語(yǔ)義。

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

    舉個(gè)例子:

    1double pi = 3.14; //A2double r = 1.0; //B3double area = pi * r * r; //C

    上面三個(gè)操作的數(shù)據(jù)依賴關(guān)系如下圖所示:

    如上圖所示,A 和 C 之間存在數(shù)據(jù)依賴關(guān)系,同時(shí) B 和 C 之間也存在數(shù)據(jù)依賴關(guān)系。因此在最終執(zhí)行的指令序列中,C 不能被重排序到 A 和 B 的前面(C 排到 A 和 B 的前面,程序的結(jié)果將會(huì)被改變)。但 A 和 B 之間沒(méi)有數(shù)據(jù)依賴關(guān)系,編譯器和處理器可以重排序 A 和 B 之間的執(zhí)行順序。下圖是該程序的兩種執(zhí)行順序:

    在計(jì)算機(jī)中,軟件技術(shù)和硬件技術(shù)有一個(gè)共同的目標(biāo):在不改變程序執(zhí)行結(jié)果的前提下,盡可能的開(kāi)發(fā)并行度。編譯器和處理器遵從這一目標(biāo),從 h 重排序?qū)Χ嗑€程的影響

    舉例:

    1class Demo {2 int a = 0;3 boolean flag = false;45 public void write {6 a = 1; //17 flag = true; //28 }910 public void read {11 if(flag) { //312 int i = a * a; //413 }14 }15}

    由于操作 1 和 2 沒(méi)有數(shù)據(jù)依賴關(guān)系,編譯器和處理器可以對(duì)這兩個(gè)操作重排序;操作 3 和操作 4 沒(méi)有數(shù)據(jù)依賴關(guān)系,編譯器和處理器也可以對(duì)這兩個(gè)操作重排序。

    1、當(dāng)操作 1 和操作 2 重排序時(shí),可能會(huì)產(chǎn)生什么效果?

    如上圖所示,操作 1 和操作 2 做了重排序。程序執(zhí)行時(shí),線程 A 首先寫(xiě)標(biāo)記變量 flag,隨后線程 B 讀這個(gè)變量。由于條件判斷為真,線程 B 將讀取變量 a。此時(shí),變量 a 還根本沒(méi)有被線程 A 寫(xiě)入,在這里多線程程序的語(yǔ)義被重排序破壞了!

    2、當(dāng)操作 3 和操作 4 重排序時(shí)會(huì)產(chǎn)生什么效果(借助這個(gè)重排序,可以順便說(shuō)明控制依賴性)。

    在程序中,操作 3 和操作 4 存在控制依賴關(guān)系。當(dāng)代碼中存在控制依賴性時(shí),會(huì)影響指令序列執(zhí)行的并行度。為此,編譯器和處理器會(huì)采用猜測(cè)(Speculation)執(zhí)行來(lái)克服控制相關(guān)性對(duì)并行度的影響。以處理器的猜測(cè)執(zhí)行為例,執(zhí)行線程 B 的處理器可以提前讀取并計(jì)算 a * a,然后把計(jì)算結(jié)果臨時(shí)保存到一個(gè)名為重排序緩沖(reorder buffer ROB)的硬件緩存中。當(dāng)接下來(lái)操作 3 的條件判斷為真時(shí),就把該計(jì)算結(jié)果寫(xiě)入變量 i 中。

    從圖中我們可以看出,猜測(cè)執(zhí)行實(shí)質(zhì)上對(duì)操作3和4做了重排序。重排序在這里破壞了多線程程序的語(yǔ)義!

    在 順序一致性

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

    順序一致性內(nèi)存模型有兩大特性:

    • 一個(gè)線程中的所有操作必須按照程序的順序來(lái)執(zhí)行。

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

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

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

    舉個(gè)例子:

    假設(shè)有兩個(gè)線程 A 和 B 并發(fā)執(zhí)行。其中 A 線程有三個(gè)操作,它們?cè)诔绦蛑械捻樞蚴牵篈1 -> A2 -> A3。B 線程也有三個(gè)操作,它們?cè)诔绦蛑械捻樞蚴牵築1 -> B2 -> B3。

    假設(shè)這兩個(gè)線程使用監(jiān)視器鎖來(lái)正確同步:A 線程的三個(gè)操作執(zhí)行后釋放監(jiān)視器鎖,隨后 B 線程獲取同一個(gè)監(jiān)視器鎖。那么程序在順序一致性模型中的執(zhí)行效果將如下圖所示:

    現(xiàn)在我們?cè)偌僭O(shè)這兩個(gè)線程沒(méi)有做同步,下面是這個(gè)未同步程序在順序一致性模型中的執(zhí)行示意圖:

    未同步程序在順序一致性模型中雖然整體執(zhí)行順序是無(wú)序的,但所有線程都只能看到一個(gè)一致的整體執(zhí)行順序。以上圖為例,線程 A 和 B 看到的執(zhí)行順序都是:B1 -> A1 -> A2 -> B2 -> A3 -> B3。之所以能得到這個(gè)保證是因?yàn)轫樞蛞恢滦詢?nèi)存模型中的每個(gè)操作必須立即對(duì)任意線程可見(jiàn)。

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

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

    下面我們對(duì)前面的示例程序用鎖來(lái)同步,看看正確同步的程序如何具有順序一致性。

    請(qǐng)看下面的示例代碼:

    1class demo {2 int a = 0;3 boolean flag = false;45 public synchronized void write { //獲取鎖6 a = 1;7 flag = true;8 } //釋放鎖910 public synchronized void read { //獲取鎖11 if(flag) {12 int i = a;13 }14 } //釋放鎖15}

    上面示例代碼中,假設(shè) A 線程執(zhí)行 write 方法后,B 線程執(zhí)行 reade 方法。這是一個(gè)正確同步的多線程程序。根據(jù)JMM規(guī)范,該程序的執(zhí)行結(jié)果將與該程序在順序一致性模型中的執(zhí)行結(jié)果相同。下面是該程序在兩個(gè)內(nèi)存模型中的執(zhí)行時(shí)序?qū)Ρ葓D:

    在順序一致性模型中,所有操作完全按程序的順序執(zhí)行。而在 JMM 中,臨界區(qū)內(nèi)的代碼可以重排序(但 JMM 不允許臨界區(qū)內(nèi)的代碼“逸出”到臨界區(qū)之外,那樣會(huì)破壞監(jiān)視器的語(yǔ)義)。JMM 會(huì)在退出臨界區(qū)和進(jìn)入臨界區(qū)這兩個(gè)關(guān)鍵時(shí)間點(diǎn)做一些特別處理,使得線程在這兩個(gè)時(shí)間點(diǎn)具有與順序一致性模型相同的內(nèi)存視圖。雖然線程 A 在臨界區(qū)內(nèi)做了重排序,但由于監(jiān)視器的互斥執(zhí)行的特性,這里的線程 B 根本無(wú)法“觀察”到線程 A 在臨界區(qū)內(nèi)的重排序。這種重排序既提高了執(zhí)行效率,又沒(méi)有改變程序的執(zhí)行結(jié)果。

    從這里我們可以看到 JMM 在具體實(shí)現(xiàn)上的基本方針:在不改變(正確同步的)程序執(zhí)行結(jié)果的前提下,盡可能的為編譯器和處理器的優(yōu)化打開(kāi)方便之門(mén)。


    未同步程序的執(zhí)行特性

    未同步程序在 JMM 中的執(zhí)行時(shí),整體上是無(wú)序的,其執(zhí)行結(jié)果無(wú)法預(yù)知。未同步程序在兩個(gè)模型中的執(zhí)行特性有下面幾個(gè)差異:

    1. 順序一致性模型保證JMM 不保證對(duì) 64 位的 long 型和 double 型變量的讀/寫(xiě)操作具有原子性,而順序一致性模型保證對(duì)所有的內(nèi)存讀/寫(xiě)操作都具有原子。

    第三個(gè)差異與處理器總線的工作機(jī)制密切相關(guān)。在計(jì)算機(jī)中,數(shù)據(jù)通過(guò)總線在處理器和內(nèi)存之間傳遞。每次處理器和內(nèi)存之間的數(shù)據(jù)傳遞都是通過(guò)總線事務(wù)來(lái)完成的??偩€事務(wù)包括讀事務(wù)和寫(xiě)事務(wù)。讀事務(wù)從內(nèi)存?zhèn)魉蛿?shù)據(jù)到處理器,寫(xiě)事務(wù)從處理器傳遞數(shù)據(jù)到內(nèi)存,每個(gè)事務(wù)會(huì)讀/寫(xiě)內(nèi)存中一個(gè)或多個(gè)物理上連續(xù)的字??偩€會(huì)同步試圖并發(fā)使用總線的事務(wù)。在一個(gè)處理器執(zhí)行總線事務(wù)期間,總線會(huì)禁止其它所有的處理器和 I/O 設(shè)備執(zhí)行內(nèi)存的讀/寫(xiě)。

    總線的工作機(jī)制:

    如上圖所示,假設(shè)處理器 A、B、和 C 同時(shí)向總線發(fā)起總線事務(wù),這時(shí)總線仲裁會(huì)對(duì)競(jìng)爭(zhēng)作出裁決,假設(shè)總線在仲裁后判定處理器 A 在競(jìng)爭(zhēng)中獲勝(總線仲裁會(huì)確保所有處理器都能公平的訪問(wèn)內(nèi)存)。此時(shí)處理器 A 繼續(xù)它的總線事務(wù),而其它兩個(gè)處理器則要等待處理器 A 的總線事務(wù)完成后才能開(kāi)始再次執(zhí)行內(nèi)存訪問(wèn)。假設(shè)在處理器 A 執(zhí)行總線事務(wù)期間(不管這個(gè)總線事務(wù)是讀事務(wù)還是寫(xiě)事務(wù)),處理器 D 向總線發(fā)起了總線事務(wù),此時(shí)處理器 D 的這個(gè)請(qǐng)求會(huì)被總線禁止。

    總線的這些工作機(jī)制可以把所有處理器對(duì)內(nèi)存的訪問(wèn)以串行化的方式來(lái)執(zhí)行;在任意時(shí)間點(diǎn),最多只能有一個(gè)處理器能訪問(wèn)內(nèi)存。這個(gè)特性確保了單個(gè)總線事務(wù)之中的內(nèi)存讀/寫(xiě)操作具有原子性。

    在一些 32 位的處理器上,如果要求對(duì) 64 位數(shù)據(jù)的寫(xiě)操作具有原子性,會(huì)有比較大的開(kāi)銷。為了照顧這種處理器,Java 語(yǔ)言規(guī)范鼓勵(lì)但不強(qiáng)求 JVM 對(duì) 64 位的 long 型變量和 double 型變量的寫(xiě)具有原子性。當(dāng) JVM 在這種處理器上運(yùn)行時(shí),會(huì)把一個(gè) 64 位 long/ double 型變量的寫(xiě)操作拆分為兩個(gè) 32 位的寫(xiě)操作來(lái)執(zhí)行。這兩個(gè) 32 位的寫(xiě)操作可能會(huì)被分配到不同的總線事務(wù)中執(zhí)行,此時(shí)對(duì)這個(gè) 64 位變量的寫(xiě)將不具有原子性。

    當(dāng)單個(gè)內(nèi)存操作不具有原子性,將可能會(huì)產(chǎn)生意想不到后果。請(qǐng)看下面示意圖:

    如上圖所示,假設(shè)處理器 A 寫(xiě)一個(gè) long 型變量,同時(shí)處理器 B 要讀這個(gè) long 型變量。處理器 A 中 64 位的寫(xiě)操作被拆分為兩個(gè) 32 位的寫(xiě)操作,且這兩個(gè) 32 位的寫(xiě)操作被分配到不同的寫(xiě)事務(wù)中執(zhí)行。同時(shí)處理器 B 中 64 位的讀操作被分配到單個(gè)的讀事務(wù)中執(zhí)行。當(dāng)處理器 A 和 B 按上圖的時(shí)序來(lái)執(zhí)行時(shí),處理器 B 將看到僅僅被處理器 A “寫(xiě)了一半“的無(wú)效值。

    注意,在 JSR -133 之前的舊內(nèi)存模型中,一個(gè) 64 位 long/ double 型變量的讀/寫(xiě)操作可以被拆分為兩個(gè) 32 位的讀/寫(xiě)操作來(lái)執(zhí)行。從 JSR -133 內(nèi)存模型開(kāi)始(即從JDK5開(kāi)始),僅僅只允許把一個(gè) 64 位 long/ double 型變量的寫(xiě)操作拆分為兩個(gè) 32 位的寫(xiě)操作來(lái)執(zhí)行,任意的讀操作在JSR -133中都必須具有原子性(即任意讀操作必須要在單個(gè)讀事務(wù)中執(zhí)行)。


    Volatile

    Volatile 特性

    舉個(gè)例子:

    1public class VolatileTest {2 volatile long a = 1L; // 使用 volatile 聲明 64 位的 long 型34 public void set(long l) {5 a = l; //單個(gè) volatile 變量的寫(xiě)6 }78 public long get {9 return a; //單個(gè) volatile 變量的讀10 }1112 public void getAndIncreament {13 a++; // 復(fù)合(多個(gè)) volatile 變量的讀 /寫(xiě)14 }15}

    假設(shè)有多個(gè)線程分別調(diào)用上面程序的三個(gè)方法,這個(gè)程序在語(yǔ)義上和下面程序等價(jià):

    1public class VolatileTest {2 long a = 1L; // 64 位的 long 型普通變量34 public synchronized void set(long l) { //對(duì)單個(gè)普通變量的寫(xiě)用同一個(gè)鎖同步5 a = l;6 }78 public synchronized long get { //對(duì)單個(gè)普通變量的讀用同一個(gè)鎖同步9 return a;10 }1112 public void getAndIncreament { //普通方法調(diào)用13 long temp = get; //調(diào)用已同步的讀方法14 temp += 1L; //普通寫(xiě)操作15 set(temp); //調(diào)用已同步的寫(xiě)方法16 }17}

    如上面示例程序所示,對(duì)一個(gè) volatile 變量的單個(gè)讀/寫(xiě)操作,與對(duì)一個(gè)普通變量的讀/寫(xiě)操作使用同一個(gè)鎖來(lái)同步,它們之間的執(zhí)行效果相同。

    鎖的 h對(duì)一個(gè) volatile 變量的讀,總是能看到(任意線程)對(duì)這個(gè) volatile 變量最后的寫(xiě)入。

    鎖的語(yǔ)義決定了臨界區(qū)代碼的執(zhí)行具有原子性。這意味著即使是 64 位的 long 型和 double 型變量,只要它是 volatile變量,對(duì)該變量的讀寫(xiě)就將具有原子性。如果是多個(gè) volatile 操作或類似于 volatile++ 這種復(fù)合操作,這些操作整體上不具有原子性。

    簡(jiǎn)而言之,volatile 變量自身具有下列特性:

    • 可見(jiàn)性。對(duì)一個(gè) volatile 變量的讀,總是能看到(任意線程)對(duì)這個(gè) volatile 變量最后的寫(xiě)入。

    • 原子性:對(duì)任意單個(gè) volatile 變量的讀/寫(xiě)具有原子性,但類似于 volatile++ 這種復(fù)合操作不具有原子性。


    volatile 寫(xiě)-讀的內(nèi)存定義

    • 當(dāng)寫(xiě)一個(gè) volatile 變量時(shí),JMM 會(huì)把該線程對(duì)應(yīng)的本地內(nèi)存中的共享變量值刷新到主內(nèi)存。

    • 當(dāng)讀一個(gè) volatile 變量時(shí),JMM 會(huì)把該線程對(duì)應(yīng)的本地內(nèi)存置為無(wú)效。線程接下來(lái)將從主內(nèi)存中讀取共享變量。

    假設(shè)上面的程序 flag 變量用 volatile 修飾


    volatile 內(nèi)存語(yǔ)義的實(shí)現(xiàn)

    下面是 JMM 針對(duì)編譯器制定的 volatile 重排序規(guī)則表:

    為了實(shí)現(xiàn) volatile 的內(nèi)存語(yǔ)義,編譯器在生成字節(jié)碼時(shí),會(huì)在指令序列中插入內(nèi)存屏障來(lái)禁止特定類型的處理器重排序。

    下面是基于保守策略的 JMM 內(nèi)存屏障插入策略:

    • 在每個(gè) volatile 寫(xiě)操作的前面插入一個(gè) StoreStore 屏障。

    • 在每個(gè) volatile 寫(xiě)操作的后面插入一個(gè) StoreLoad 屏障。

    • 在每個(gè) volatile 讀操作的后面插入一個(gè) LoadLoad 屏障。

    • 在每個(gè) volatile 讀操作的后面插入一個(gè) LoadStore 屏障。

    下面是保守策略下,volatile 寫(xiě)操作 插入內(nèi)存屏障后生成的指令序列示意圖:

    下面是在保守策略下,volatile 讀操作 插入內(nèi)存屏障后生成的指令序列示意圖:

    上述 volatile 寫(xiě)操作和 volatile 讀操作的內(nèi)存屏障插入策略非常保守。在實(shí)際執(zhí)行時(shí),只要不改變 volatile 寫(xiě)-讀的內(nèi)存語(yǔ)義,編譯器可以根據(jù)具體情況省略不必要的屏障。


    鎖釋放和獲取的內(nèi)存語(yǔ)義

    當(dāng)線程釋放鎖時(shí),JMM 會(huì)把該線程對(duì)應(yīng)的本地內(nèi)存中的共享變量刷新到主內(nèi)存中。

    當(dāng)線程獲取鎖時(shí),JMM 會(huì)把該線程對(duì)應(yīng)的本地內(nèi)存置為無(wú)效。從而使得被監(jiān)視器保護(hù)的臨界區(qū)代碼必須要從主內(nèi)存中去讀取共享變量。


    鎖內(nèi)存語(yǔ)義的實(shí)現(xiàn)

    借助 ReentrantLock 來(lái)講解,PS:后面專門(mén)講下這塊(ReentrantLock、Synchronized、公平鎖、非公平鎖、AQS等),可以看看大明哥的博客:[http://cmsblogs concurrent 包的實(shí)現(xiàn)

    如果我們仔細(xì)分析 concurrent 包的源代碼實(shí)現(xiàn),會(huì)發(fā)現(xiàn)一個(gè)通用化的實(shí)現(xiàn)模式:

    1. 首先,聲明共享變量為 volatile;

    2. 然后,使用 CAS 的原子條件更新來(lái)實(shí)現(xiàn)線程之間的同步;

    3. 同時(shí),配合以 volatile 的讀/寫(xiě)和 CAS 所具有的 volatile 讀和寫(xiě)的內(nèi)存語(yǔ)義來(lái)實(shí)現(xiàn)線程之間的通信。

    AQS,非阻塞數(shù)據(jù)結(jié)構(gòu)和原子變量類(java.util.concurrent.atomic 包中的類),這些 concurrent 包中的基礎(chǔ)類都是使用這種模式來(lái)實(shí)現(xiàn)的,而 concurrent 包中的高層類又是依賴于這些基礎(chǔ)類來(lái)實(shí)現(xiàn)的。從整體來(lái)看,concurrent 包的實(shí)現(xiàn)示意圖如下:


    final

    對(duì)于 final 域,編譯器和處理器要遵守兩個(gè)重排序規(guī)則:

    1. 在構(gòu)造函數(shù)內(nèi)對(duì)一個(gè) final 域的寫(xiě)入,與隨后把這個(gè)被構(gòu)造對(duì)象的引用賦值給一個(gè)引用變量,這兩個(gè)操作之間不能重排序。

    2. 初次讀一個(gè)包含 final 域的對(duì)象的引用,與隨后初次讀這個(gè) final 域,這兩個(gè)操作之間不能重排序。


    寫(xiě) final 域的重排序規(guī)則

    寫(xiě) final 域的重排序規(guī)則禁止把 final 域的寫(xiě)重排序到構(gòu)造函數(shù)之外。這個(gè)規(guī)則的實(shí)現(xiàn)包含下面2個(gè)方面:

    • JMM 禁止編譯器把 final 域的寫(xiě)重排序到構(gòu)造函數(shù)之外。

    • 編譯器會(huì)在 final 域的寫(xiě)之后,構(gòu)造函數(shù) return 之前,插入一個(gè) StoreStore 屏障。這個(gè)屏障禁止處理器把 final 域的寫(xiě)重排序到構(gòu)造函數(shù)之外。


    讀 final 域的重排序規(guī)則

    在一個(gè)線程中,初次讀對(duì)象引用與初次讀該對(duì)象包含的 final 域,JMM 禁止處理器重排序這兩個(gè)操作(注意,這個(gè)規(guī)則僅僅針對(duì)處理器)。編譯器會(huì)在讀 final 域操作的前面插入一個(gè) LoadLoad 屏障。


    final 域是引用類型

    對(duì)于引用類型,寫(xiě) final 域的重排序規(guī)則對(duì)編譯器和處理器增加了如下約束:

    在構(gòu)造函數(shù)內(nèi)對(duì)一個(gè) final 引用的對(duì)象的成員域的寫(xiě)入,與隨后在構(gòu)造函數(shù)外把這個(gè)被構(gòu)造對(duì)象的引用賦值給一個(gè)引用變量,這兩個(gè)操作之間不能重排序。


    總結(jié)

    JMM,處理器內(nèi)存模型與順序一致性內(nèi)存模型之間的關(guān)系

    JMM 是一個(gè)語(yǔ)言級(jí)的內(nèi)存模型,處理器內(nèi)存模型是硬件級(jí)的內(nèi)存模型,順序一致性內(nèi)存模型是一個(gè)理論參考模型。下面是語(yǔ)言內(nèi)存模型,處理器內(nèi)存模型和順序一致性內(nèi)存模型的強(qiáng)弱對(duì)比示意圖:


    JMM 的設(shè)計(jì)示意圖


    JMM 的內(nèi)存可見(jiàn)性保證

    Java 程序的內(nèi)存可見(jiàn)性保證按程序類型可以分為下列三類:

    1.zhisheng)里回復(fù) 面經(jīng)、ES、Flink、 Spring、Java、Kafka、監(jiān)控 等關(guān)鍵字可以查看更多關(guān)鍵字對(duì)應(yīng)的文章


    Flink 實(shí)戰(zhàn)

    1、《從0到1學(xué)習(xí)Flink》—— Apache Flink 介紹

    2、《從0到1學(xué)習(xí)Flink》—— Mac 上搭建 Flink 1.6.0 環(huán)境并構(gòu)建運(yùn)行簡(jiǎn)單程序入門(mén)

    3、《從0到1學(xué)習(xí)Flink》—— Flink 配置文件詳解

    4、《從0到1學(xué)習(xí)Flink》—— Data Source 介紹

    5、《從0到1學(xué)習(xí)Flink》—— 如何自定義 Data Source ?

    6、《從0到1學(xué)習(xí)Flink》—— Data Sink 介紹

    7、《從0到1學(xué)習(xí)Flink》—— 如何自定義 Data Sink ?

    8、《從0到1學(xué)習(xí)Flink》—— Flink Data transformation(轉(zhuǎn)換)

    9、《從0到1學(xué)習(xí)Flink》—— 介紹 Flink 中的 Stream Windows

    10、《從0到1學(xué)習(xí)Flink》—— Flink 中的幾種 Time 詳解

    11、《從0到1學(xué)習(xí)Flink》—— Flink 讀取 Kafka 數(shù)據(jù)寫(xiě)入到 ElasticSearch

    12、《從0到1學(xué)習(xí)Flink》—— Flink 項(xiàng)目如何運(yùn)行?

    13、《從0到1學(xué)習(xí)Flink》—— Flink 讀取 Kafka 數(shù)據(jù)寫(xiě)入到 Kafka

    14、《從0到1學(xué)習(xí)Flink》—— Flink JobManager 高可用性配置

    15、《從0到1學(xué)習(xí)Flink》—— Flink parallelism 和 Slot 介紹

    16、《從0到1學(xué)習(xí)Flink》—— Flink 讀取 Kafka 數(shù)據(jù)批量寫(xiě)入到 MySQL

    17、《從0到1學(xué)習(xí)Flink》—— Flink 讀取 Kafka 數(shù)據(jù)寫(xiě)入到 RabbitMQ

    18、《從0到1學(xué)習(xí)Flink》—— 你上傳的 jar 包藏到哪里去了

    19、大數(shù)據(jù)“重磅炸彈”——實(shí)時(shí)計(jì)算框架 Flink

    20、《Flink 源碼解析》—— 源碼編譯運(yùn)行

    21、為什么說(shuō)流處理即未來(lái)?

    22、OPPO數(shù)據(jù)中臺(tái)之基石:基于Flink SQL構(gòu)建實(shí)數(shù)據(jù)倉(cāng)庫(kù)

    23、流計(jì)算框架 Flink 與 Storm 的性能對(duì)比

    24、Flink狀態(tài)管理和容錯(cuò)機(jī)制介紹

    25、原理解析 | Apache Flink  Flink 源碼解析

    網(wǎng)站題目:深入理解 Java 內(nèi)存模型
    文章分享:http://chinadenli.net/news14/104314.html

    成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供做網(wǎng)站、域名注冊(cè)、網(wǎng)站設(shè)計(jì)品牌網(wǎng)站制作、云服務(wù)器、Google

    廣告

    聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

    成都定制網(wǎng)站網(wǎng)頁(yè)設(shè)計(jì)
    99秋霞在线观看视频| 亚洲综合激情另类专区老铁性| 国产精品日本女优在线观看| 国产又粗又硬又大又爽的视频| 中文字幕欧美精品人妻一区| 国产又猛又黄又粗又爽无遮挡| 国产在线成人免费高清观看av| 久久黄片免费播放大全| 亚洲av日韩一区二区三区四区| 国产精品午夜福利在线观看| 久久精品亚洲情色欧美| 中文字幕五月婷婷免费| 国产免费人成视频尤物| 九九热视频免费在线视频| 欧美日韩黑人免费观看| 精品香蕉国产一区二区三区| 激情综合网俺也狠狠地| 免费一区二区三区少妇| 中文精品人妻一区二区| 人体偷拍一区二区三区| 麻豆印象传媒在线观看| 欧美一区二区三区高潮菊竹| 精品少妇人妻一区二区三区| 午夜精品一区免费视频| 亚洲精选91福利在线观看| 91麻豆精品欧美视频| 日韩人妻中文字幕精品| 国产一区二区精品丝袜| 国产日韩欧美国产欧美日韩 | 欧美性高清一区二区三区视频| 日韩精品一区二区不卡| 99久久精品国产麻豆| 免费在线播放不卡视频| 99一级特黄色性生活片| 欧美午夜视频免费观看| 午夜福利国产精品不卡| 国产亚洲精品久久99| 清纯少妇被捅到高潮免费观看| 日韩欧美国产精品自拍| 老司机精品国产在线视频| 国产精品午夜福利免费在线|