<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          不會(huì)Java內(nèi)存模型,就先別扯什么熟悉并發(fā)編程

          共 8542字,需瀏覽 18分鐘

           ·

          2020-06-23 23:21



          本文公眾號(hào)來(lái)源:JavaKeeper作者:大猿帥本文已收錄至我的GitHub

          ??? 面試官:
          • 你知道 Java 內(nèi)存模型 JMM 嗎?那你知道它的三大特性嗎?
          • Java 是如何解決指令重排問(wèn)題的?
          • 既然CPU有緩存一致性協(xié)議(MESI),為什么 JMM 還需要volatile關(guān)鍵字?

          前兩天看到同學(xué)和我顯擺他們公司配的電腦多好多好,我默默打開(kāi)了自己的電腦,酷睿 i7-4770,也不是不夠用嘛,4 核 8 線程的 CPU,也是杠杠的。

          5a52e03fad90961af3d75a9e6acf6b43.webp

          扯這玩意干啥,Em~~~~

          介紹 Java 內(nèi)存模型之前,先溫習(xí)下計(jì)算機(jī)硬件內(nèi)存模型

          硬件內(nèi)存架構(gòu)

          計(jì)算機(jī)在執(zhí)行程序的時(shí)候,每條指令都是在 CPU 中執(zhí)行的,而執(zhí)行的時(shí)候,又免不了要和數(shù)據(jù)打交道。而計(jì)算機(jī)上面的數(shù)據(jù),是存放在主存當(dāng)中的,也就是計(jì)算機(jī)的物理內(nèi)存。

          計(jì)算機(jī)硬件架構(gòu)簡(jiǎn)易圖:

          eb6233dd8dec761ada62ccd5cd6baf64.webp

          我們以多核 CPU 為例,每個(gè)CPU 核都包含一組 「CPU 寄存器」,這些寄存器本質(zhì)上是在 CPU 內(nèi)存中。CPU 在這些寄存器上執(zhí)行操作的速度要比在主內(nèi)存(RAM)中執(zhí)行的速度快得多。

          因?yàn)?strong>CPU速率高, 內(nèi)存速率慢,為了讓存儲(chǔ)體系可以跟上CPU的速度,所以中間又加上 Cache 層,就是我們說(shuō)的 「CPU 高速緩存」。

          CPU多級(jí)緩存

          由于CPU的運(yùn)算速度遠(yuǎn)遠(yuǎn)超越了1級(jí)緩存的數(shù)據(jù)I\O能力,CPU廠商又引入了多級(jí)的緩存結(jié)構(gòu)。通常L1、L2 是每個(gè)CPU 核有一個(gè),L3 是多個(gè)核共用一個(gè)。

          Cache Line

          Cache又是由很多個(gè)「緩存行」(Cache line) 組成的。Cache line 是 Cache 和 RAM 交換數(shù)據(jù)的最小單位。

          Cache 存儲(chǔ)數(shù)據(jù)是固定大小為單位的,稱為一個(gè)Cache entry,這個(gè)單位稱為Cache lineCache block。給定Cache 容量大小和 Cache line size 的情況下,它能存儲(chǔ)的條目個(gè)數(shù)(number of cache entries)就是固定的。因?yàn)镃ache 是固定大小的,所以它從主內(nèi)存獲取數(shù)據(jù)也是固定大小。對(duì)于X86來(lái)講,是 64Bytes。對(duì)于ARM來(lái)講,較舊的架構(gòu)的Cache line是32Bytes,但一次內(nèi)存訪存只訪問(wèn)一半的數(shù)據(jù)也不太合適,所以它經(jīng)常是一次填兩個(gè) Cache line,叫做 double fill。

          緩存的工作原理

          這里的緩存的工作原理和我們項(xiàng)目中用 memcached、redis 做常用數(shù)據(jù)的緩存層是一個(gè)道理。

          當(dāng) CPU 要讀取一個(gè)數(shù)據(jù)時(shí),首先從緩存中查找,如果找到就立即讀取并送給CPU處理;如果沒(méi)有找到,就去內(nèi)存中讀取并送給 CPU 處理,同時(shí)把這個(gè)數(shù)據(jù)所在的數(shù)據(jù)塊(就是我們上邊說(shuō)的 Cache block)調(diào)入緩存中,即把臨近的共 64 Byte 的數(shù)據(jù)也一同載入,因?yàn)榕R近的數(shù)據(jù)在將來(lái)被訪問(wèn)的可能性更大,可以使得以后對(duì)整塊數(shù)據(jù)的讀取都從緩存中進(jìn)行,不必再調(diào)用內(nèi)存。

          這就增加了CPU讀取緩存的命中率(Cache hit)了。

          計(jì)算機(jī)層級(jí)存儲(chǔ)

          計(jì)算機(jī)存儲(chǔ)系統(tǒng)是有層次結(jié)構(gòu)的,類似一個(gè)金字塔,頂層的寄存器讀寫速度較高,但是空間較小。底層的讀寫速度較低,但是空間較大

          000fc210ffd319d5a4ae6b2540396d94.webp

          緩存一致性

          既然每個(gè)核中都有單獨(dú)的緩存,那我的 4 核 8 線程 CPU 處理主內(nèi)存數(shù)據(jù)的時(shí)候,不就會(huì)出現(xiàn)數(shù)據(jù)不一致問(wèn)題了嗎?

          為了解決這個(gè)問(wèn)題,先后有過(guò)兩種方法:總線鎖機(jī)制緩存鎖機(jī)制

          總線鎖就是使用 CPU 提供的一個(gè)LOCK#信號(hào),當(dāng)一個(gè)處理器在總線上輸出此信號(hào),其他處理器的請(qǐng)求將被阻塞,那么該處理器就可以獨(dú)占共享鎖。這樣就保證了數(shù)據(jù)一致性。

          但是總線鎖開(kāi)銷太大,我們需要控制鎖的粒度,所以又有了緩存鎖,核心就是“緩存一致性協(xié)議”,不同的 CPU 硬件廠商實(shí)現(xiàn)方式稍有不同,有MSI、MESI、MOSI等。

          代碼亂序執(zhí)行優(yōu)化

          為了使得處理器內(nèi)部的運(yùn)算單元盡量被充分利用,提高運(yùn)算效率,處理器可能會(huì)對(duì)輸入的代碼進(jìn)行「亂序執(zhí)行」(Out-Of-Order Execution),處理器會(huì)在計(jì)算之后將亂序執(zhí)行的結(jié)果重組,亂序優(yōu)化可以保證在單線程下該執(zhí)行結(jié)果與順序執(zhí)行的結(jié)果是一致的,但不保證程序中各個(gè)語(yǔ)句計(jì)算的先后順序與輸入代碼中的順序一致。

          亂序執(zhí)行技術(shù)是處理器為提高運(yùn)算速度而做出違背代碼原有順序的優(yōu)化。在單核時(shí)代,處理器保證做出的優(yōu)化不會(huì)導(dǎo)致執(zhí)行結(jié)果遠(yuǎn)離預(yù)期目標(biāo),但在多核環(huán)境下卻并非如此。

          多核環(huán)境下, 如果存在一個(gè)核的計(jì)算任務(wù)依賴另一個(gè)核的計(jì)算任務(wù)的中間結(jié)果,而且對(duì)相關(guān)數(shù)據(jù)讀寫沒(méi)做任何防護(hù)措施,那么其順序性并不能靠代碼的先后順序來(lái)保證,處理器最終得出的結(jié)果和我們邏輯得到的結(jié)果可能會(huì)大不相同。

          編譯器指令重排

          除了上述由處理器和緩存引起的亂序之外,現(xiàn)代編譯器同樣提供了亂序優(yōu)化。之所以出現(xiàn)編譯器亂序優(yōu)化其根本原因在于處理器每次只能分析一小塊指令,但編譯器卻能在很大范圍內(nèi)進(jìn)行代碼分析,從而做出更優(yōu)的策略,充分利用處理器的亂序執(zhí)行功能。

          內(nèi)存屏障

          盡管我們看到亂序執(zhí)行初始目的是為了提高效率,但是它看來(lái)其好像在這多核時(shí)代不盡人意,其中的某些”自作聰明”的優(yōu)化導(dǎo)致多線程程序產(chǎn)生各種各樣的意外。因此有必要存在一種機(jī)制來(lái)消除亂序執(zhí)行帶來(lái)的壞影響,也就是說(shuō)應(yīng)該允許程序員顯式的告訴處理器對(duì)某些地方禁止亂序執(zhí)行。這種機(jī)制就是所謂內(nèi)存屏障。不同架構(gòu)的處理器在其指令集中提供了不同的指令來(lái)發(fā)起內(nèi)存屏障,對(duì)應(yīng)在編程語(yǔ)言當(dāng)中就是提供特殊的關(guān)鍵字來(lái)調(diào)用處理器相關(guān)的指令,JMM里我們?cè)偬接憽?/p>


          Java內(nèi)存模型

          Java 內(nèi)存模型即 Java Memory Model,簡(jiǎn)稱 JMM

          這里的內(nèi)存模型可不是 JVM 里的運(yùn)行時(shí)數(shù)據(jù)區(qū)。

          「內(nèi)存模型」可以理解為在特定操作協(xié)議下,對(duì)特定的內(nèi)存或高速緩存進(jìn)行讀寫訪問(wèn)的過(guò)程抽象

          不同架構(gòu)的物理計(jì)算機(jī)可以有不一樣的內(nèi)存模型,Java虛擬機(jī)也有自己的內(nèi)存模型。

          Java虛擬機(jī)規(guī)范中試圖定義一種「 Java 內(nèi)存模型」來(lái)屏蔽掉各種硬件和操作系統(tǒng)的內(nèi)存訪問(wèn)差異,以實(shí)現(xiàn)讓 Java 程序在各種平臺(tái)下都能達(dá)到一致的內(nèi)存訪問(wèn)效果,不必因?yàn)椴煌脚_(tái)上的物理機(jī)的內(nèi)存模型的差異,對(duì)各平臺(tái)定制化開(kāi)發(fā)程序。

          Java 內(nèi)存模型的主要目標(biāo)是定義程序中各個(gè)變量的訪問(wèn)規(guī)則,即在虛擬機(jī)中將變量存儲(chǔ)到內(nèi)存和從內(nèi)存中取出變量這樣的底層細(xì)節(jié)。這里的變量與我們寫 Java 代碼中的變量不同,它包括了實(shí)例字段、靜態(tài)字段和構(gòu)成數(shù)組對(duì)象的元素,但不包括局部變量和方法參數(shù),因?yàn)樗麄兪蔷€程私有的,不會(huì)被共享。

          JMM 組成

          • 主內(nèi)存:Java 內(nèi)存模型規(guī)定了所有變量都存儲(chǔ)在主內(nèi)存(Main Memory)中(此處的主內(nèi)存與物理硬件的主內(nèi)存RAM 名字一樣,兩者可以互相類比,但此處僅是虛擬機(jī)內(nèi)存的一部分)。

          • 工作內(nèi)存:每條線程都有自己的工作內(nèi)存(Working Memory,又稱本地內(nèi)存,可與CPU高速緩存類比),線程的工作內(nèi)存中保存了該線程使用到的主內(nèi)存中的共享變量的副本拷貝。線程對(duì)變量的所有操作都必須在工作內(nèi)存進(jìn)行,而不能直接讀寫主內(nèi)存中的變量工作內(nèi)存是 JMM 的一個(gè)抽象概念,并不真實(shí)存在

          e6d52c34f7adc4c5f341ecc185099208.webp

          JMM 與 JVM 內(nèi)存結(jié)構(gòu)

          JMM 與 Java 內(nèi)存區(qū)域中的堆、棧、方法區(qū)等并不是同一個(gè)層次的內(nèi)存劃分,兩者基本沒(méi)有關(guān)系。如果一定要勉強(qiáng)對(duì)應(yīng),那從變量、主內(nèi)存、工作內(nèi)存的定義看,主內(nèi)存主要對(duì)應(yīng) Java 堆中的對(duì)象實(shí)例數(shù)據(jù)部分,工作內(nèi)存則對(duì)應(yīng)虛擬機(jī)棧的部分區(qū)域(與上圖對(duì)應(yīng)著看哈)。

          07be0f9464b958da792bf4ad0e34c442.webp

          JMM 與計(jì)算機(jī)內(nèi)存結(jié)構(gòu)

          Java 內(nèi)存模型和硬件內(nèi)存體系結(jié)構(gòu)也沒(méi)有什么關(guān)系。硬件內(nèi)存體系結(jié)構(gòu)不區(qū)分棧和堆。在硬件上,線程棧和堆都位于主內(nèi)存中。線程棧和堆的一部分有時(shí)可能出現(xiàn)在高速緩存和CPU寄存器中。如下圖所示:

          0f381f7042e52c2317b328ebd2550d7a.webp

          當(dāng)對(duì)象和變量可以存儲(chǔ)在計(jì)算機(jī)中不同的內(nèi)存區(qū)域時(shí),這就可能會(huì)出現(xiàn)某些問(wèn)題。兩個(gè)主要問(wèn)題是:

          • 線程更新(寫)到共享變量的可見(jiàn)性
          • 讀取、檢查和寫入共享變量時(shí)的競(jìng)爭(zhēng)條件

          可見(jiàn)性問(wèn)題(Visibility of Shared Objects)

          如果兩個(gè)或多個(gè)線程共享一個(gè)對(duì)象,則一個(gè)線程對(duì)共享對(duì)象的更新可能對(duì)其他線程不可見(jiàn)(當(dāng)然可以用 Java 提供的關(guān)鍵字 volatile)。假設(shè)共享對(duì)象最初存儲(chǔ)在主內(nèi)存中。在 CPU 1上運(yùn)行的線程將共享對(duì)象讀入它的CPU緩存后修改,但是還沒(méi)來(lái)得及即刷新回主內(nèi)存,這時(shí)其他 CPU 上運(yùn)行的線程就不會(huì)看到共享對(duì)象的更改。這樣,每個(gè)線程都可能以自己的線程結(jié)束,就出現(xiàn)了可見(jiàn)性問(wèn)題,如下

          b3cf1cf4c72c4159bcc83a8cc1443191.webp

          競(jìng)爭(zhēng)條件(Race Conditions)

          這個(gè)其實(shí)就是我們常說(shuō)的原子問(wèn)題。

          如果兩個(gè)或多個(gè)線程共享一個(gè)對(duì)象,并且多個(gè)線程更新該共享對(duì)象中的變量,則可能出現(xiàn)競(jìng)爭(zhēng)條件。

          想象一下,如果線程A將一個(gè)共享對(duì)象的變量讀入到它的CPU緩存中。此時(shí),線程B執(zhí)行相同的操作,但是進(jìn)入不同的CPU緩存。現(xiàn)在線程A執(zhí)行 +1 操作,線程B也這樣做。現(xiàn)在該變量增加了兩次,在每個(gè)CPU緩存中一次。

          如果這些增量是按順序執(zhí)行的,則變量結(jié)果會(huì)是3,并將原始值+ 2寫回主內(nèi)存。但是,這兩個(gè)增量是同時(shí)執(zhí)行的,沒(méi)有適當(dāng)?shù)耐健2还軐⒛膫€(gè)線程的結(jié)構(gòu)寫回主內(nèi)存,更新后的值只比原始值高1,顯然是有問(wèn)題的。如下(當(dāng)然可以用 Java 提供的關(guān)鍵字 Synchronized)

          6d85fe67db477c0e679e05b39f8f5e06.webp

          JMM 特性

          JMM 就是用來(lái)解決如上問(wèn)題的。JMM是圍繞著并發(fā)過(guò)程中如何處理可見(jiàn)性、原子性和有序性這 3 個(gè) 特征建立起來(lái)的

          • 可見(jiàn)性:可見(jiàn)性是指當(dāng)一個(gè)線程修改了共享變量的值,其他線程能夠立即得知這個(gè)修改。Java 中的 volatile、synchronzied、final 都可以實(shí)現(xiàn)可見(jiàn)性

          • 原子性:即一個(gè)操作或者多個(gè)操作,要么全部執(zhí)行并且執(zhí)行的過(guò)程不會(huì)被任何因素打斷,要么就都不執(zhí)行。即使在多個(gè)線程一起執(zhí)行的時(shí)候,一個(gè)操作一旦開(kāi)始,就不會(huì)被其他線程所干擾。

          • 有序性

            計(jì)算機(jī)在執(zhí)行程序時(shí),為了提高性能,編譯器和處理器常常會(huì)對(duì)指令做重排,一般分為以下 3 種

            6ab44fecffa0973d1fda4eefb4276ed0.webp

            單線程環(huán)境里確保程序最終執(zhí)行結(jié)果和代碼順序執(zhí)行的結(jié)果一致;

            處理器在進(jìn)行重排序時(shí)必須要考慮指令之間的數(shù)據(jù)依賴性

            多線程環(huán)境中線程交替執(zhí)行,由于編譯器優(yōu)化重排的存在,兩個(gè)線程中使用的變量能否保證一致性是無(wú)法確定的,結(jié)果無(wú)法預(yù)測(cè)

          內(nèi)存之間的交互操作

          關(guān)于主內(nèi)存和工作內(nèi)存之間具體的交互協(xié)議,即一個(gè)變量如何從主內(nèi)存拷貝到工作內(nèi)存、如何從工作內(nèi)存同步回主內(nèi)存之類的實(shí)現(xiàn)細(xì)節(jié),Java 內(nèi)存模型中定義了 8 種 操作來(lái)完成,虛擬機(jī)實(shí)現(xiàn)必須保證每一種操作都是原子的、不可再拆分的(double和long類型例外)

          • lock(鎖定):作用于主內(nèi)存的變量,它把一個(gè)變量標(biāo)識(shí)為一條線程獨(dú)占的狀態(tài)。
          • unlock(解鎖):作用于主內(nèi)存的變量,它把一個(gè)處于鎖定狀態(tài)的變量釋放出來(lái),釋放后的變量才可以被其他線程鎖定。
          • read(讀取):作用于主內(nèi)存的變量,它把一個(gè)變量的值從主內(nèi)存?zhèn)鬏數(shù)骄€程的工作內(nèi)存中,以便隨后的load動(dòng)作使用。
          • load(載入):作用于工作內(nèi)存的變量,它把read操作從主內(nèi)存中得到的變量值放入工作內(nèi)存的變量副本中。
          • use(使用):作用于工作內(nèi)存的變量,它把工作內(nèi)存中一個(gè)變量的值傳遞給執(zhí)行引擎,每當(dāng)虛擬機(jī)遇到一個(gè)需要使用到變量的值的字節(jié)碼指令時(shí)將會(huì)執(zhí)行這個(gè)操作。
          • assign(賦值):作用于工作內(nèi)存的變量,它把一個(gè)從執(zhí)行引擎接收到的值賦給工作內(nèi)存的變量,每當(dāng)虛擬機(jī)遇到一個(gè)給變量賦值的字節(jié)碼指令時(shí)執(zhí)行這個(gè)操作。
          • store(存儲(chǔ)):作用于工作內(nèi)存的變量,它把工作內(nèi)存中一個(gè)變量的值傳送到主內(nèi)存中,以便隨后的write操作使用。
          • write(寫入):作用于主內(nèi)存的變量,它把store操作從工作內(nèi)存中得到的變量的值放入主內(nèi)存的變量中。

          如果需要把一個(gè)變量從主內(nèi)存復(fù)制到工作內(nèi)存,那就要順序地執(zhí)行 read 和 load 操作,如果要把變量從工作內(nèi)存同步回主內(nèi)存,就要順序地執(zhí)行 store 和 write 操作。注意,Java 內(nèi)存模型只要求上述兩個(gè)操作必須按順序執(zhí)行,而沒(méi)有保證是連續(xù)執(zhí)行。也就是說(shuō) read 與 load 之間、store 與write 之間是可插入其他指令的,如對(duì)主內(nèi)存中的變量 a、b 進(jìn)行訪問(wèn)時(shí),一種可能出現(xiàn)順序是 read a、read b、load b、load a。

          8291fbbf4f483261fcd28d340119abf7.webp

          除此之外,Java 內(nèi)存模型還規(guī)定了在執(zhí)行上述 8 種基本操作時(shí)必須滿足如下規(guī)則

          • 不允許 read 和 load、store 和 write 操作之一單獨(dú)出現(xiàn),即不允許一個(gè)變量從主內(nèi)存讀取了但工作內(nèi)存不接受,或者從工作內(nèi)存發(fā)起回寫了但主內(nèi)存不接受的情況出現(xiàn)。
          • 不允許一個(gè)線程丟棄它的最近的 assign 操作,即變量在工作內(nèi)存中改變了之后必須把該變化同步回主內(nèi)存。
          • 不允許一個(gè)線程無(wú)原因地(沒(méi)有發(fā)生過(guò)任何 assign 操作)把數(shù)據(jù)從線程的工作內(nèi)存同步回主內(nèi)存。
          • 一個(gè)新的變量只能在主內(nèi)存中“誕生”,不允許在工作內(nèi)存中直接使用一個(gè)未被初始化(load 或 assign)的變量,換句話說(shuō),就是對(duì)一個(gè)變量實(shí)施 use、store 操作之前,必須先執(zhí)行過(guò)了 assign 和 load 操作。
          • 一個(gè)變量在同一時(shí)刻只允許一條線程對(duì)其進(jìn)行 lock 操作,但 lock 操作可以被同一條線程重復(fù)執(zhí)行多次,多次執(zhí)行 lock 后,只有執(zhí)行相同次數(shù)的 unlock 操作,變量才會(huì)被解鎖。
          • 如果對(duì)一個(gè)變量執(zhí)行 lock 操作,那將會(huì)清空工作內(nèi)存中此變量的值,在執(zhí)行引擎使用這個(gè)變量前,需要重新執(zhí)行 load 或 assign 操作初始化變量的值。
          • 如果一個(gè)變量事先沒(méi)有被 lock 操作鎖定,那就不允許對(duì)它執(zhí)行 unlock 操作,也不允許去 unlock 一個(gè)被其他線程鎖定住的變量。
          • 對(duì)一個(gè)變量執(zhí)行 unlock 操作之前,必須先把此變量同步回主內(nèi)存中(執(zhí)行 store、write 操作)。

          long 和 double 型變量的特殊規(guī)則

          Java 內(nèi)存模型要求 lock,unlock,read,load,assign,use,store,write 這 8 個(gè)操作都具有原子性,但對(duì)于64 位的數(shù)據(jù)類型( long 或 double),在模型中定義了一條相對(duì)寬松的規(guī)定,允許虛擬機(jī)將沒(méi)有被 volatile 修飾的 64 位數(shù)據(jù)的讀寫操作劃分為兩次 32 位的操作來(lái)進(jìn)行,即允許虛擬機(jī)實(shí)現(xiàn)選擇可以不保證 64 位數(shù)據(jù)類型的load,store,read,write 這 4 個(gè)操作的原子性,即 long 和 double 的非原子性協(xié)定

          如果多線程的情況下double 或 long 類型并未聲明為 volatile,可能會(huì)出現(xiàn)“半個(gè)變量”的數(shù)值,也就是既非原值,也非修改后的值。

          雖然 Java 規(guī)范允許上面的實(shí)現(xiàn),但商用虛擬機(jī)中基本都采用了原子性的操作,因此在日常使用中幾乎不會(huì)出現(xiàn)讀取到“半個(gè)變量”的情況,so,這個(gè)了解下就行。

          先行發(fā)生原則

          先行發(fā)生(happens-before)是 Java 內(nèi)存模型中定義的兩項(xiàng)操作之間的偏序關(guān)系,如果操作A 先行發(fā)生于操作B,那么A的結(jié)果對(duì)B可見(jiàn)。happens-before關(guān)系的分析需要分為單線程和多線程的情況:

          • 單線程下的 happens-before 字節(jié)碼的先后順序天然包含happens-before關(guān)系:因?yàn)閱尉€程內(nèi)共享一份工作內(nèi)存,不存在數(shù)據(jù)一致性的問(wèn)題。在程序控制流路徑中靠前的字節(jié)碼 happens-before 靠后的字節(jié)碼,即靠前的字節(jié)碼執(zhí)行完之后操作結(jié)果對(duì)靠后的字節(jié)碼可見(jiàn)。然而,這并不意味著前者一定在后者之前執(zhí)行。實(shí)際上,如果后者不依賴前者的運(yùn)行結(jié)果,那么它們可能會(huì)被重排序。
          • 多線程下的 happens-before 多線程由于每個(gè)線程有共享變量的副本,如果沒(méi)有對(duì)共享變量做同步處理,線程1更新執(zhí)行操作A共享變量的值之后,線程2開(kāi)始執(zhí)行操作B,此時(shí)操作A產(chǎn)生的結(jié)果對(duì)操作B不一定可見(jiàn)。

          為了方便程序開(kāi)發(fā),Java 內(nèi)存模型實(shí)現(xiàn)了下述的先行發(fā)生關(guān)系:

          • 程序次序規(guī)則: 一個(gè)線程內(nèi),按照代碼順序,書寫在前面的操作先行發(fā)生于書寫在后面的操作。
          • 管程鎖定規(guī)則: 一個(gè)unLock操作先行發(fā)生于后面對(duì)同一個(gè)鎖的lock操作。
          • volatile變量規(guī)則: 對(duì)一個(gè)變量的寫操作 happens-before 后面對(duì)這個(gè)變量的讀操作。
          • 傳遞規(guī)則: 如果操作A 先行發(fā)生于操作B,而操作B又先行發(fā)生于操作C,則可以得出操作A 先行發(fā)生于操作C。
          • 線程啟動(dòng)規(guī)則: Thread對(duì)象的start()方法先行發(fā)生于此線程的每個(gè)一個(gè)動(dòng)作。
          • 線程中斷規(guī)則: 對(duì)線程interrupt()方法的調(diào)用先行發(fā)生于被中斷線程的代碼檢測(cè)到中斷事件的發(fā)生。
          • 線程終結(jié)規(guī)則: 線程中所有的操作都先行發(fā)生于線程的終止檢測(cè),我們可以通過(guò)Thread.join()方法結(jié)束、Thread.isAlive()的返回值手段檢測(cè)到線程已經(jīng)終止執(zhí)行。
          • 對(duì)象終結(jié)規(guī)則: 一個(gè)對(duì)象的初始化完成先行發(fā)生于它的 finalize()方法的開(kāi)始

          內(nèi)存屏障

          上邊的一系列操作保證了數(shù)據(jù)一致性,Java中如何保證底層操作的有序性和可見(jiàn)性?可以通過(guò)內(nèi)存屏障。

          內(nèi)存屏障是被插入兩個(gè) CPU 指令之間的一種指令,用來(lái)禁止處理器指令發(fā)生重排序(像屏障一樣),從而保障有序性的。另外,為了達(dá)到屏障的效果,它也會(huì)使處理器寫入、讀取值之前,將主內(nèi)存的值寫入高速緩存,清空無(wú)效隊(duì)列,從而保障可見(jiàn)性

          eg:

          Store1;
          Store2;
          Load1;
          StoreLoad; //內(nèi)存屏障
          Store3;
          Load2;
          Load3;

          對(duì)于上面的一組 CPU 指令(Store表示寫入指令,Load表示讀取指令),StoreLoad 屏障之前的 Store 指令無(wú)法與StoreLoad 屏障之后的 Load 指令進(jìn)行交換位置,即重排序。但是 StoreLoad 屏障之前和之后的指令是可以互換位置的,即 Store1 可以和 Store2 互換,Load2 可以和 Load3 互換。

          常見(jiàn)的 4 種屏障

          • LoadLoad 屏障:對(duì)于這樣的語(yǔ)句 Load1; LoadLoad; Load2,在Load2及后續(xù)讀取操作要讀取的數(shù)據(jù)被訪問(wèn)前,保證Load1要讀取的數(shù)據(jù)被讀取完畢。
          • StoreStore 屏障:對(duì)于這樣的語(yǔ)句 Store1; StoreStore; Store2,在Store2及后續(xù)寫入操作執(zhí)行前,保證Store1的寫入操作對(duì)其它處理器可見(jiàn)。
          • LoadStore 屏障:對(duì)于這樣的語(yǔ)句Load1; LoadStore; Store2,在Store2及后續(xù)寫入操作被執(zhí)行前,保證Load1要讀取的數(shù)據(jù)被讀取完畢。
          • StoreLoad 屏障:對(duì)于這樣的語(yǔ)句Store1; StoreLoad; Load2,在Load2及后續(xù)所有讀取操作執(zhí)行前,保證Store1的寫入對(duì)所有處理器可見(jiàn)。它的開(kāi)銷是四種屏障中最大的(沖刷寫緩沖器,清空無(wú)效化隊(duì)列)。在大多數(shù)處理器的實(shí)現(xiàn)中,這個(gè)屏障也被稱為全能屏障,兼具其它三種內(nèi)存屏障的功能。

          Java 中對(duì)內(nèi)存屏障的使用在一般的代碼中不太容易見(jiàn)到,常見(jiàn)的有 volatile 和 synchronized 關(guān)鍵字修飾的代碼塊,還可以通過(guò) Unsafe 這個(gè)類來(lái)使用內(nèi)存屏障。(下一章扯扯這些)

          Java 內(nèi)存模型就是通過(guò)定義的這些來(lái)解決可見(jiàn)性、原子性和有序性的。

          各類知識(shí)點(diǎn)總結(jié)

          下面的文章都有對(duì)應(yīng)的原創(chuàng)精美PDF,在持續(xù)更新中,可以來(lái)找我催更~

          掃碼或者微信搜Java3y 免費(fèi)領(lǐng)取原創(chuàng)思維導(dǎo)圖、精美PDF。在公眾號(hào)回復(fù)「888」領(lǐng)取,PDF內(nèi)容純手打有任何不懂歡迎來(lái)問(wèn)我。


          
           

          原創(chuàng)電子書
          c036bef87bf6b05716f496ee69bca775.webp

          原創(chuàng)思維導(dǎo)圖

          fbb33f38f40a5293a242fce56a0a6878.webp


          7937977e2dea4abf71afde244c55f29b.webp

          a451aadf494bdba2083abd59af23b749.webp

          306ad95856fb111d86493771a8ed8e78.webp

          我是三歪,一個(gè)想要變強(qiáng)的男人,感謝大家的點(diǎn)贊收藏和轉(zhuǎn)發(fā),下期見(jiàn)。
          瀏覽 59
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  无码高清毛片在线观看 | 青春草免费视频 | 最新三级片在线播放 | 美女考比视频 | 亚洲蜜桃一区二区 |