亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Java 內(nèi)存模型(JVM)

 更新時(shí)間:2021年08月24日 17:08:06   作者:mghio  
本文公國(guó)講解Java 內(nèi)存模型來(lái)看看解決可見(jiàn)性、有序性問(wèn)題的 Java 內(nèi)存模型(JMM),今天通過(guò)本文給大家介紹Java 內(nèi)存模型(JVM)的相關(guān)知識(shí),感興趣的朋友一起看看吧

前言

在并發(fā)編程中,當(dāng)多個(gè)線程同時(shí)訪問(wèn)同一個(gè)共享的可變變量時(shí),會(huì)產(chǎn)生不確定的結(jié)果,所以要編寫(xiě)線程安全的代碼,其本質(zhì)上是對(duì)這些可變的共享變量的訪問(wèn)操作進(jìn)行管理。導(dǎo)致這種不確定結(jié)果的原因就是可見(jiàn)性、有序性和原子性問(wèn)題,Java 為解決可見(jiàn)性和有序性問(wèn)題引入了 Java 內(nèi)存模型,使用互斥方案(其核心實(shí)現(xiàn)技術(shù)是鎖)來(lái)解決原子性問(wèn)題。這篇先來(lái)看看解決可見(jiàn)性、有序性問(wèn)題的 Java 內(nèi)存模型(JMM)。

一、什么是 Java 內(nèi)存模型

Java 內(nèi)存模型定義如下:

內(nèi)存模型限制的是共享變量,也就是存儲(chǔ)在堆內(nèi)存中的變量,在 Java 語(yǔ)言中,所有的實(shí)例變量、靜態(tài)變量和數(shù)組元素都存儲(chǔ)在堆內(nèi)存之中。而方法參數(shù)、異常處理參數(shù)這些局部變量存儲(chǔ)在方法棧幀之中,因此不會(huì)在線程之間共享,不會(huì)受到內(nèi)存模型影響,也不存在內(nèi)存可見(jiàn)性問(wèn)題。

通常,在線程之間的通訊方式有共享內(nèi)存和消息傳遞兩種,很明顯,Java 采用的是第一種即共享的內(nèi)存模型,在共享的內(nèi)存模型里,多線程之間共享程序的公共狀態(tài),通過(guò)讀-寫(xiě)內(nèi)存的方式來(lái)進(jìn)行隱式通訊。

從抽象的角度來(lái)看,JMM 其實(shí)是定義了線程和主內(nèi)存之間的關(guān)系,首先,多個(gè)線程之間的共享變量存儲(chǔ)在主內(nèi)存之中,同時(shí)每個(gè)線程都有一個(gè)自己私有的本地內(nèi)存,本地內(nèi)存中存儲(chǔ)著該線程讀或?qū)懝蚕碜兞康母北荆ㄗ⒁猓罕镜貎?nèi)存是 JMM 定義的抽象概念,實(shí)際上并不存在)。抽象模型如下圖所示:

在這個(gè)抽象的內(nèi)存模型中,在兩個(gè)線程之間的通信(共享變量狀態(tài)變更)時(shí),會(huì)進(jìn)行如下兩個(gè)步驟:

  1. 線程 A 把在本地內(nèi)存更新后的共享變量副本的值,刷新到主內(nèi)存中。
  2. 線程 B 在使用到該共享變量時(shí),到主內(nèi)存中去讀取線程 A 更新后的共享變量的值,并更新線程 B 本地內(nèi)存的值。

JMM 本質(zhì)上是在硬件(處理器)內(nèi)存模型之上又做了一層抽象,使得應(yīng)用開(kāi)發(fā)人員只需要了解 JMM 就可以編寫(xiě)出正確的并發(fā)代碼,而無(wú)需過(guò)多了解硬件層面的內(nèi)存模型。

二、為什么需要 Java 內(nèi)存模型

在日常的程序開(kāi)發(fā)中,為一些共享變量賦值的場(chǎng)景會(huì)經(jīng)常碰到,假設(shè)一個(gè)線程為整型共享變量 count 做賦值操作(count = 9527;),此時(shí)就會(huì)有一個(gè)問(wèn)題,其它讀取該共享變量的線程在什么情況下獲取到的變量值為 9527 呢?如果缺少同步的話,會(huì)有很多因素導(dǎo)致其它讀取該變量的線程無(wú)法立即甚至是永遠(yuǎn)都無(wú)法看到該變量的最新值。

比如緩存就可能會(huì)改變寫(xiě)入共享變量副本提交到主內(nèi)存的次序,保存在本地緩存的值,對(duì)于其它線程是不可見(jiàn)的;編譯器為了優(yōu)化性能,有時(shí)候會(huì)改變程序中語(yǔ)句執(zhí)行的先后順序,這些因素都有可能會(huì)導(dǎo)致其它線程無(wú)法看到共享變量的最新值。

在文章開(kāi)頭,提到了 JMM 主要是為了解決可見(jiàn)性有序性問(wèn)題,那么首先就要先搞清楚,導(dǎo)致可見(jiàn)性和有序性問(wèn)題發(fā)生的本質(zhì)原因是什么?現(xiàn)在的服務(wù)絕大部分都是運(yùn)行在多核 CPU 的服務(wù)器上,每顆 CPU 都有自己的緩存,這時(shí) CPU 緩存與內(nèi)存的數(shù)據(jù)就會(huì)有一致性問(wèn)題了,當(dāng)一個(gè)線程對(duì)共享變量的修改,另外一個(gè)線程無(wú)法立刻看到。導(dǎo)致可見(jiàn)性問(wèn)題的本質(zhì)原因是緩存

有序性是指代碼實(shí)際的執(zhí)行順序和代碼定義的順序一致,編譯器為了優(yōu)化性能,雖然會(huì)遵守 as-if-serial 語(yǔ)義(不管怎么重排序,在單線程下的執(zhí)行結(jié)果不能改變),不過(guò)有時(shí)候編譯器及解釋器的優(yōu)化也可能引發(fā)一些問(wèn)題。比如:雙重檢查來(lái)創(chuàng)建單實(shí)例對(duì)象。下面是使用雙重檢查來(lái)實(shí)現(xiàn)延遲創(chuàng)建單例對(duì)象的代碼:

/**
 * @author mghio
 * @since 2021-08-22
 */
public class DoubleCheckedInstance {

  private static DoubleCheckedInstance instance;

  public static DoubleCheckedInstance getInstance() {
    if (instance == null) {
      synchronized (DoubleCheckedInstance.class) {
        if (instance == null) {
          instance = new DoubleCheckedInstance();
        }
      }
    }

    return instance;
  }
  
}

這里的 instance = new DoubleCheckedInstance();,看起來(lái) Java 代碼只有一行,應(yīng)該是無(wú)法就行重排序的,實(shí)際上其編譯后的實(shí)際指令是如下三步:

  1. 分配對(duì)象的內(nèi)存空間
  2. 初始化對(duì)象
  3. 設(shè)置 instance 指向剛剛已經(jīng)分配的內(nèi)存地址

上面的第 2 步和第 3 步如果改變執(zhí)行順序也不會(huì)改變單線程的執(zhí)行結(jié)果,也就是說(shuō)可能會(huì)發(fā)生重排序,下圖是一種多線程并發(fā)執(zhí)行的場(chǎng)景:

此時(shí)線程 B 獲取到的 instance 是沒(méi)有初始化過(guò)的,如果此來(lái)訪問(wèn) instance 的成員變量就可能觸發(fā)空指針異常。導(dǎo)致有序性問(wèn)題的本質(zhì)原因是編譯器優(yōu)化。那你可能會(huì)想既然緩存和編譯器優(yōu)化是導(dǎo)致可見(jiàn)性問(wèn)題和有序性問(wèn)題的原因,那直接禁用掉不就可以徹底解決這些問(wèn)題了嗎,但是如果這么做了的話,程序的性能可能就會(huì)受到比較大的影響了。

其實(shí)可以換一種思路,能不能把這些禁用緩存和編譯器優(yōu)化的權(quán)利交給編碼的工程師來(lái)處理,他們肯定最清楚什么時(shí)候需要禁用,這樣就只需要提供按需禁用緩存和編譯優(yōu)化的方法即可,使用比較靈活。因此Java 內(nèi)存模型就誕生了,它規(guī)范了 JVM 如何提供按需禁用緩存和編譯優(yōu)化的方法,規(guī)定了 JVM 必須遵守一組最小的保證,這個(gè)最小保證規(guī)定了線程對(duì)共享變量的寫(xiě)入操作何時(shí)對(duì)其它線程可見(jiàn)。

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

順序一致性模型是一個(gè)理想化后的理論參考模型,處理器和編程語(yǔ)言的內(nèi)存模型的設(shè)計(jì)都是參考的順序一致性模型理論。其有如下兩大特性:

  1. 一個(gè)線程中的所有操作必須按照程序的順序來(lái)執(zhí)行
  2. 所有的線程都只能看到一個(gè)單一的執(zhí)行操作順序,不管程序是否同步

在工程師視角下的順序一致性模型如下:

順序一致性模型有一個(gè)單一的全局內(nèi)存,這個(gè)全局內(nèi)存可以通過(guò)左右搖擺的開(kāi)關(guān)可以連接到任意一個(gè)線程,每個(gè)線程都必須按照程序的順序來(lái)執(zhí)行內(nèi)存的讀和寫(xiě)操作。該理想模型下,任務(wù)時(shí)刻都只能有一個(gè)線程可以連接到內(nèi)存,當(dāng)多個(gè)線程并發(fā)執(zhí)行時(shí),就可以通過(guò)開(kāi)關(guān)就可以把多個(gè)線程的讀和寫(xiě)操作串行化。

順序一致性模型中,所有操操作完全按照順序串行執(zhí)行,但是在 JMM 中就沒(méi)有這個(gè)保證了,未同步的程序在 JMM 中不僅程序的執(zhí)行順序是無(wú)序的,而且由于本地內(nèi)存的存在,所有線程看到的操作順序也可能會(huì)不一致,比如一個(gè)線程把寫(xiě)共享變量保存在本地內(nèi)存中,在還沒(méi)有刷新到主內(nèi)存前,其它線程是不可見(jiàn)的,只有更新到主內(nèi)存后,其它線程才有可能看到。

JMM 對(duì)在正確同步的程序做了順序一致性的保證,也就是程序的執(zhí)行結(jié)果和該程序在順序一致性內(nèi)存模型中的執(zhí)行結(jié)果相同。

四、Happens-Before 規(guī)則

Happens-Before 規(guī)則是 JMM 中的核心概念,Happens-Before 概念最開(kāi)始在 這篇論文 提出,其在論文中使用 Happens-Before 來(lái)定義分布式系統(tǒng)之間的偏序關(guān)系。在 JSR-133 中使用 Happens-Before 來(lái)指定兩個(gè)操作之間的執(zhí)行順序。

JMM 正是通過(guò)這個(gè)規(guī)則來(lái)保證跨線程的內(nèi)存可見(jiàn)性,Happens-Before 的含義是前面一個(gè)對(duì)共享變量的操作結(jié)果對(duì)該變量的后續(xù)操作是可見(jiàn)的,約束了編譯器的優(yōu)化行為,雖然允許編譯器優(yōu)化,但是優(yōu)化后的代碼必須要滿足 Happens-Before 規(guī)則,這個(gè)規(guī)則給工程師做了這個(gè)保證:同步的多線程程序是按照 Happens-Before 指定的順序來(lái)執(zhí)行的。目的就是為了在不改變程序(單線程或者正確同步的多線程程序)執(zhí)行結(jié)果的前提下,盡最大可能的提高程序執(zhí)行的效率。

JSR-133 規(guī)范中定了如下 6 項(xiàng) Happens-Before 規(guī)則:

  1. 程序順序規(guī)則:一個(gè)線程中的每個(gè)操作,Happens-Before 該線程中的任意后續(xù)操作
  2. 監(jiān)視器鎖規(guī)則:對(duì)一個(gè)鎖的解鎖操作,Happens-Before 于后面對(duì)這個(gè)鎖的加鎖操作
  3. volatile 規(guī)則:對(duì)一個(gè) volatile 類(lèi)型的變量的寫(xiě)操作,Happens-Before 與任意后面對(duì)這個(gè) volatile 變量的讀操作
  4. 傳遞性規(guī)則:如果操作 A Happens-Before 于操作 B,并且操作 B Happens-Before 于操作 C,則操作 A Happens-Before 于操作 C
  5. start() 規(guī)則:如果一個(gè)線程 A 執(zhí)行操作 threadB.start() 啟動(dòng)線程 B,那么線程 A 的 start() 操作 Happens-Before 于線程 B 的任意操作
  6. join() 規(guī)則:如果線程 A 執(zhí)行操作 threadB.join() 并成功返回,那么線程 B 中的任意操作 Happens-Before 于線程 A 從 threadB.join() 操作成功返回

JMM 的一個(gè)基本原則是:只要不改變單線程和正確同步的多線程的執(zhí)行結(jié)果,編譯器和處理器隨便怎么優(yōu)化都可以,實(shí)際上對(duì)于應(yīng)用開(kāi)發(fā)人員對(duì)于兩個(gè)操作是否真的被重排序并不關(guān)心,真正關(guān)心的是執(zhí)行結(jié)果不能被修改。因此 Happens-Before 本質(zhì)上和 sa-if-serial 的語(yǔ)義是一致的,只是 sa-if-serial 只是保證在單線程下的執(zhí)行結(jié)果不被改變。

總結(jié):
本文主要介紹了內(nèi)存模型的相關(guān)基礎(chǔ)知識(shí)和相關(guān)概念,JMM 屏蔽了不同處理器內(nèi)存模型之間的差異,在不同的處理器平臺(tái)上給應(yīng)用開(kāi)發(fā)人員抽象出了統(tǒng)一的 Java 內(nèi)存模型(JMM)。常見(jiàn)的處理器內(nèi)存模型比 JMM 的要弱,因此 JVM 會(huì)在生成字節(jié)碼指令時(shí)在適當(dāng)?shù)奈恢貌迦雰?nèi)存屏障(內(nèi)存屏障的類(lèi)型會(huì)因處理器平臺(tái)而有所不同)來(lái)限制部分重排序。更多關(guān)于Java 內(nèi)存模型的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!,希望大家以后多多支持腳本之家!

相關(guān)文章

  • 深入解析HashMap的put方法

    深入解析HashMap的put方法

    在Java集合中,HashMap的重要性不言而喻,作為一種存儲(chǔ)鍵值對(duì)的數(shù)據(jù)結(jié)構(gòu),它在日常開(kāi)發(fā)中有著非常多的應(yīng)用場(chǎng)景,也是面試中的高頻考點(diǎn),本篇文章就來(lái)分析一下HashMap集合中的put方法
    2022-01-01
  • 利用反射實(shí)現(xiàn)Excel和CSV 轉(zhuǎn)換為Java對(duì)象功能

    利用反射實(shí)現(xiàn)Excel和CSV 轉(zhuǎn)換為Java對(duì)象功能

    將Excel或CSV文件轉(zhuǎn)換為Java對(duì)象(POJO)以及將Java對(duì)象轉(zhuǎn)換為Excel或CSV文件可能是一個(gè)復(fù)雜的過(guò)程,但如果使用正確的工具和技術(shù),這個(gè)過(guò)程就會(huì)變得十分簡(jiǎn)單,在本文中,我們將了解如何利用一個(gè)Java反射的庫(kù)來(lái)實(shí)現(xiàn)這個(gè)功能,需要的朋友可以參考下
    2023-11-11
  • Java實(shí)現(xiàn)兩人五子棋游戲(二) 畫(huà)出棋盤(pán)

    Java實(shí)現(xiàn)兩人五子棋游戲(二) 畫(huà)出棋盤(pán)

    這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)兩人五子棋游戲,畫(huà)出五子棋的棋盤(pán),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-03-03
  • SpringBoot使用Jsp的示例代碼

    SpringBoot使用Jsp的示例代碼

    這篇文章主要介紹了SpringBoot使用Jsp的示例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-08-08
  • 使用Netty快速實(shí)現(xiàn)一個(gè)群聊功能的示例詳解

    使用Netty快速實(shí)現(xiàn)一個(gè)群聊功能的示例詳解

    這篇文章主要為大家詳細(xì)介紹了如何利用?Netty?框架開(kāi)發(fā)一個(gè)?WebSocket?服務(wù)端,從而實(shí)現(xiàn)一個(gè)簡(jiǎn)單的在線聊天功能,感興趣的小伙伴可以了解下
    2023-11-11
  • Java8中Lambda表達(dá)式的理解與應(yīng)用

    Java8中Lambda表達(dá)式的理解與應(yīng)用

    Java8最值得學(xué)習(xí)的特性就是Lambda表達(dá)式和Stream?API,如果有python或者javascript的語(yǔ)言基礎(chǔ),對(duì)理解Lambda表達(dá)式有很大幫助,下面這篇文章主要給大家介紹了關(guān)于Java8中Lambda表達(dá)式的相關(guān)資料,需要的朋友可以參考下
    2022-02-02
  • 教你如何使用Java實(shí)現(xiàn)WebSocket

    教你如何使用Java實(shí)現(xiàn)WebSocket

    這篇文章主要介紹了教你如何使用Java實(shí)現(xiàn)WebSocket問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-06-06
  • java為什么不建議用equals判斷對(duì)象相等

    java為什么不建議用equals判斷對(duì)象相等

    本文主要介紹了java為什么不建議用equals判斷對(duì)象相等,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-03-03
  • 詳解SpringCloud Finchley Gateway 統(tǒng)一異常處理

    詳解SpringCloud Finchley Gateway 統(tǒng)一異常處理

    這篇文章主要介紹了詳解SpringCloud Finchley Gateway 統(tǒng)一異常處理,非常具有實(shí)用價(jià)值,需要的朋友可以參考下
    2018-10-10
  • Java通過(guò)exchange協(xié)議發(fā)送郵件

    Java通過(guò)exchange協(xié)議發(fā)送郵件

    這篇文章主要為大家詳細(xì)介紹了Java通過(guò)exchange協(xié)議發(fā)送郵件,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-02-02

最新評(píng)論