久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

Java 內(nèi)存模型

 精品唯居 2021-05-02

本書部分摘自《Java 并發(fā)編程的藝術(shù)》


線程通信與同步

在并發(fā)編程中,有兩個需要處理的關(guān)鍵問題:

  • 線程之間如何通信
  • 線程之間如何同步

通信指線程之間以何種機制來交換信息,,通信機制有兩種:

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

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


Java 內(nèi)存模型

前面提到線程的通信與同步問題,,Java 線程之間的通信由 Java 內(nèi)存模型(簡稱 JMM)控制,,JMM 決定一個線程對共享變量的寫入何時對另一個線程可見

線程之間的共享變量存儲在主內(nèi)存,每個線程都一個私有的本地內(nèi)存,,本地內(nèi)存中存儲了該線程以 讀/寫 共享變量的副本

Java 內(nèi)存模型的抽象示意如圖

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

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

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


重排序

在執(zhí)行程序時,為了提高性能,,編譯器和處理器常常會對指令做重排序,。重排序可能會導致線程程序出現(xiàn)內(nèi)存可見性問題,下面分別介紹三種類型的重排序以及它們對內(nèi)存可見性的影響:

  1. 編譯器優(yōu)化的重排序

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

  2. 指令級并行的重排序

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

  3. 內(nèi)存系統(tǒng)的重排序

    由于處理器使用緩存和 讀/寫 緩沖區(qū),這使得加載和存儲操作看上去可能是在亂序執(zhí)行

    這里對第三種情況做個詳細解釋,,現(xiàn)代處理器使用緩沖區(qū)臨時保存向內(nèi)存寫入的數(shù)據(jù),,此舉可以保證指令流水線持續(xù)進行,避免由于處理器停頓下來等待向內(nèi)存寫入數(shù)據(jù)而產(chǎn)生延遲,。但每個處理器上的寫緩沖區(qū),,僅僅對它所在的處理器可見,這個特性可能會導致處理器對內(nèi)存的 讀/寫 操作執(zhí)行順序不一定與內(nèi)存實際發(fā)生的 讀/寫 操作順序一致,。為了說明情況,,請看下表:

    Processor A Processor B
    代碼 a = 1; // A1
    x = b; // A2
    b = 2; // B1
    y = a; // B2

    處理器 A 和處理器 B 按程序的順序并行執(zhí)行內(nèi)存訪問,最終可能得到 x = y = 0 的結(jié)果,,具體原因如圖

    處理器 A 和 處理器 B 同時把共享變量寫入自己的緩沖區(qū)(A1,、B1),然后從內(nèi)存中讀取另一個共享變量(A2,、B2),,最后才把緩存區(qū)中保存的臟數(shù)據(jù)刷新到內(nèi)存中(A3、B3),,這種情況下,,程序最后就得到 x = y = 0 的結(jié)果

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


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

由此可見,,JMM 不能任由重排序發(fā)生,,必須加以控制,否則會引發(fā)線程不安全問題,。為了更好地解釋 JMM 為保證內(nèi)存可見性所采取的措施,,首先介紹一些基礎(chǔ)概念

1. 數(shù)據(jù)依賴性

如果兩個操作訪問同一個變量,且這兩個操作有一個為寫操作,,此時這兩個操作之間就存在數(shù)據(jù)依賴性,,只要重排序這兩個操作的執(zhí)行順序,程序的執(zhí)行結(jié)果就會被改變,。編譯器和處理器在重排序時,,會遵守數(shù)據(jù)依賴性,不會改變存在數(shù)據(jù)依賴關(guān)系的兩個操作的執(zhí)行順序,。數(shù)據(jù)依賴分為下列三種類型:

名稱 代碼示例 說明
寫后讀 a = 1;
b = a;
寫一個變量之后,,再讀這個位置
寫后寫 a = 1;
a = 2;
寫一個變量之后,再寫這個變量
讀后寫 a = b;
b = 1;
讀一個變量之后,,再寫這個變量

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

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

2. as-if-serial 語義

as-if-serial 語義的意思是:不管怎么重排序,,(單線程)程序的執(zhí)行結(jié)果不能被改變,。編譯器、runtime 和處理器都必須遵守 as-if-serial 語義,,為了遵守 as-if-serial 語義,,編譯器和處理器不會對存在數(shù)據(jù)依賴性關(guān)系的操作做重排序

as-if-serial 語義把單線程程序保護起來,給程序員創(chuàng)建了一個幻覺:單線程程序是按程序的順序來執(zhí)行的,,程序員無需擔心重排序會干擾他們,,也無需擔心內(nèi)存可見性問題

3. happens-before 原則

happens-before 是 JMM 最核心的概念,對于 Java 程序員來說,,理解 happens-before 是理解 JMM 的關(guān)鍵,。

從 JDK5 開始,Java 使用新的 JSR-133 內(nèi)存模型,,JSR-133 使用 happens-before 的概念來闡述操作之間的內(nèi)存可見性,。在 JMM 中,如果一個操作執(zhí)行的結(jié)果需要對另一個操作可見,,那么這兩個操作之間必須存在 happens-before 關(guān)系,,這兩個操作既可以是在一個線程之內(nèi),也可以是不同線程之間

A happens-before B,,就是 A 操作先于 B 操作執(zhí)行,。當然這種說法并不準確,兩個操作之間具有 happens-before 關(guān)系,,僅僅要求前一個操作(執(zhí)行的結(jié)果)對后一個操作可見,,且前一個操作按順序排在第二個操作之前

在 JMM 中定義了 happens-before 的原則如下:

  • 單線程 happens-before 原則:在同一個線程中,書寫在前面的操作 happens-before 后面的操作
  • 鎖的 happens-before 原則:同一個鎖的 unlock 操作 happens-before 此鎖的 lock 操作
  • volatile 的 happens-before 原則:對一個 volatile 變量的寫操作 happens-before 對此變量的任意操作(當然也包括寫操作)
  • happens-before 的傳遞性原則:如果 A 操作 happens-before B 操作,,B 操作 happens-before C 操作,,那么 A 操作 happens-before C 操作
  • 線程啟動的 happens-before 原則:同一個線程的 start 方法 happens-before 此線程的其它方法
  • 線程中斷的 happens-before 原則:對線程 interrupt 方法的調(diào)用 happens-before 被中斷線程的檢測到中斷發(fā)送的代碼
  • 線程終結(jié)的 happens-before 原則:線程中的所有操作都 happens-before 線程的終止檢測
  • 對象創(chuàng)建的 happens-before 原則:一個對象的初始化完成先于他的 finalize 方法調(diào)用

有關(guān) happens-before 每一個原則的實現(xiàn),這里不再具體闡述,,只要知道有這么一回事就好了

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

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

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

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

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

為了更好的理解,下面我們通過兩個示意圖來對順序一致性模型的特性做進一步的說明

假設(shè)有兩個線程 A 和 B 并發(fā)執(zhí)行,,其中 A 線程有三個操作,,它們在程序中的順序是:A1 -> A2 -> A3,B 線程也有三個操作,,它們在程序中的順序是:B1 -> B2 -> B3

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

現(xiàn)在再假設(shè)這兩個線程沒有做同步,,下面是這個未同步程序在順序一致性模型中的執(zhí)行示意圖:

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

5. 總結(jié)

由于重排序的存在,JMM 不可能實現(xiàn)順序一致性內(nèi)存模型,,同時也不可能完全禁止重排序,,因為這樣會影響效率。一方面,,程序員希望內(nèi)存模型易于理解,、易于編程,希望基于一個強內(nèi)存模型來編寫代碼,;另一方面,,編譯器和處理器希望內(nèi)存模型對它們的束縛越少越好,這樣它們就可以做盡可能多的優(yōu)化來提高性能,,編譯器和處理器希望實現(xiàn)一個弱內(nèi)存模型,。這兩個因素相互矛盾,所以關(guān)鍵在于找到一個平衡點

平衡的關(guān)鍵在于優(yōu)化重排序規(guī)則,,根據(jù)前面提到的 happens-before 原則,、數(shù)據(jù)依賴性以及 as-if-serial 原則等規(guī)定了編譯器和處理器什么情況允許重排序,什么情況不允許重排序,。對于會改變程序執(zhí)行結(jié)果的重排序,,JMM 要求編譯器和處理器必須禁止這種重排序,否則不作要求,。于是程序員所看到的就是一個保證了內(nèi)存可見性的可靠的內(nèi)存模型

下圖是 JMM 的設(shè)計示意圖

從上圖我們也可以發(fā)現(xiàn),,JMM 會遵循一個基本原則:只要不改變程序的執(zhí)行結(jié)果(指的是單線程程序和正確同步的多線程程序),編譯器和處理器怎么優(yōu)化都可以,。例如,,如果編譯器經(jīng)過細致地分析后,認定一個鎖只會被單個線程訪問,,那么這個鎖可以被消除,。再如,,如果編譯器經(jīng)過細致的分析后,認定一個 volatile 變量只會被單個線程訪問,,那么編譯器可以把這個 volatile 變量當作一個普通變量來對待,。這些優(yōu)化既不會改變程序的執(zhí)行結(jié)果,又能提高程序的執(zhí)行效率,。而從程序員的角度來看,,程序員其實并不關(guān)心重排序是否真的發(fā)生,程序員關(guān)心的是只程序執(zhí)行時的語義不能被改變而已


    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,,所有內(nèi)容均由用戶發(fā)布,,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式,、誘導購買等信息,,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,,請點擊一鍵舉報,。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多