0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Java運(yùn)行時(shí)內(nèi)存區(qū)域與硬件內(nèi)存的關(guān)系2

jf_78858299 ? 來源:小牛呼嚕嚕 ? 作者:小牛呼嚕嚕 ? 2023-02-09 14:41 ? 次閱讀

線程間通信

線程間的通信一般有兩種方式進(jìn)行,一是通過消息傳遞,二是共享內(nèi)存。Java 線程間的通信采用的是共享內(nèi)存方式,JMM 為共享變量提供了線程間的保障。如果兩個(gè)線程都對一個(gè)共享變量進(jìn)行操作,共享變量初始值為 1,每個(gè)線程都變量進(jìn)行加 1,預(yù)期共享變量的值為 3。在 JMM 規(guī)范下會有一系列的操作。我們直接來看下圖:

在多線程的情況下,對主內(nèi)存中的共享變量進(jìn)行操作可能發(fā)生線程安全問題,比如:線程 1 和線程 2 同時(shí)對同一個(gè)共享變量進(jìn)行操作,執(zhí)行+1操作,線程 1 、線程2 讀取的共享變量是否是彼此修改前還是修改后的值呢,這個(gè)是無法確定的,這種情況和CPU的高速緩存與內(nèi)存之間的問題非常相似

如何實(shí)現(xiàn)主內(nèi)存與工作內(nèi)存的變量同步,為了更好的控制主內(nèi)存和本地內(nèi)存的交互,Java 內(nèi)存模型定義了八種操作來實(shí)現(xiàn):

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

重溫Java 并發(fā)三大特性

原子性

原子性:即一個(gè)或者多個(gè)操作作為一個(gè)整體,要么全部執(zhí)行,要么都不執(zhí)行,并且操作在執(zhí)行過程中不會被線程調(diào)度機(jī)制打斷;而且這種操作一旦開始,就一直運(yùn)行到結(jié)束,中間不會有任何上下文切換(context switch) 比如:

int i = 0;   //語句1,原子性

i++;         //語句2,非原子性

語句1大家一幕了然,語句2卻許多人容易犯迷糊,i++ 其實(shí)可以分為3步:

  1. i 被從局部變量表(內(nèi)存)取出,
  2. 壓入操作棧(寄存器),操作棧中自增
  3. 使用棧頂值更新局部變量表(寄存器更新寫入內(nèi)存)

執(zhí)行上述3個(gè)步驟的時(shí)候是可以進(jìn)行線程切換的,或者說是可以被另其他線程的 這3 步打斷的,因此語句2不是一個(gè)原子性操作

在 Java 中,可以借助synchronized 、各種 Lock 以及各種原子類實(shí)現(xiàn)原子性。synchronized 和各種Lock是通過保證任一時(shí)刻只有一個(gè)線程訪問該代碼塊,因此可以保證其原子性。各種原子類是利用CAS (compare and swap)操作(可能也會用到 volatile或者final關(guān)鍵字)來保證原子操作。

可見性

可見性是指當(dāng)多個(gè)線程訪問同一個(gè)變量時(shí),一個(gè)線程修改了這個(gè)變量的值,其他線程能夠立即看到修改的值。我們來看一個(gè)例子:

public class VisibilityTest {
    private boolean flag = true;

    public void change() {
        flag = false;
        System.out.println(Thread.currentThread().getName() + ",已修改flag=false");
    }

    public void load() {
        System.out.println(Thread.currentThread().getName() + ",開始執(zhí)行.....");
        int i = 0;
        while (flag) {
            i++;
        }
        System.out.println(Thread.currentThread().getName() + ",結(jié)束循環(huán)");
    }

    public static void main(String[] args) throws InterruptedException {
        VisibilityTest test = new VisibilityTest();

        // 線程threadA模擬數(shù)據(jù)加載場景
        Thread threadA = new Thread(() -> test.load(), "threadA");
        threadA.start();

        // 讓threadA執(zhí)行一會兒
        Thread.sleep(1000);
        // 線程threadB 修改 共享變量flag
        Thread threadB = new Thread(() -> test.change(), "threadB");
        threadB.start();

    }
}

threadA 負(fù)責(zé)循環(huán),threadB負(fù)責(zé)修改 共享變量flag,如果flag=false時(shí),threadA 會結(jié)束循環(huán),但是上面的例子會死循環(huán)。原因是threadA無法立即讀取到共享變量flag修改后的值。我們只需 private volatile boolean flag = true;加上volatile關(guān)鍵字threadA就可以立即退出循環(huán)了。

Java中的volatile關(guān)鍵字提供了一個(gè)功能,那就是被其修飾的變量在被修改后可以立即同步到主內(nèi)存,被其修飾的變量在每次是用之前都從主內(nèi)存刷新。

因此,可以使用volatile來保證多線程操作時(shí)變量的可見性。除了volatile,Java中的synchronizedfinal兩個(gè)關(guān)鍵字 以及各種 Lock也可以實(shí)現(xiàn)可見性。

有序性

有序性:即程序執(zhí)行的順序按照代碼的先后順序執(zhí)行。

int i = 0;
int j = 0;
i = 10;   //語句1
j = 1;    //語句2

但由于指令重排序問題,代碼的執(zhí)行順序未必就是編寫代碼時(shí)候的順序。語句可能的執(zhí)行順序如下:

  1. 語句1 語句2
  2. 語句2 語句1

指令重排對于非原子性的操作,在不影響最終結(jié)果的情況下,其拆分成的原子操作可能會被重新排列執(zhí)行順序。 指令重排不會影響單線程的執(zhí)行結(jié)果,但是會影響多線程并發(fā)執(zhí)行的結(jié)果正確性 。在Java 中,可以通過volatile關(guān)鍵字來禁止指令進(jìn)行重排序優(yōu)化,詳情可見:https://mp.weixin.qq.com/s/TyiCfVMeeDwa-2hd9N9XJQ。也可以使用synchronized關(guān)鍵字保證同一時(shí)刻只允許一條線程訪問程序塊。


參考資料

《java并發(fā)編程實(shí)戰(zhàn)》

https://www.cnblogs.com/czwbig/p/11127124.html

https://www.cnblogs.com/jelly12345/p/14609657.html

https://www.cnblogs.com/bailiyi/p/11967396.html

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報(bào)投訴
  • JAVA
    +關(guān)注

    關(guān)注

    19

    文章

    2952

    瀏覽量

    104489
  • 編譯器
    +關(guān)注

    關(guān)注

    1

    文章

    1617

    瀏覽量

    49017
  • JVM
    JVM
    +關(guān)注

    關(guān)注

    0

    文章

    157

    瀏覽量

    12197
收藏 人收藏

    評論

    相關(guān)推薦

    Labview 運(yùn)行時(shí)內(nèi)存增加

    的dll,用庫函數(shù)直接調(diào)用這個(gè)dll后,Labview運(yùn)行時(shí)所占的內(nèi)存基本上保持在0.9 M左右,不會卡死了。附件里是那個(gè)網(wǎng)友上傳的dll,大家可以下載后將.jpg改為.dll
    發(fā)表于 05-19 14:38

    C6748內(nèi)存分配運(yùn)行時(shí)出錯(cuò)

    因?yàn)樾枰獙Υ罅繑?shù)據(jù)進(jìn)行處理,我設(shè)置一個(gè)指針,內(nèi)存分配1000*sizeof(float),運(yùn)行時(shí)出錯(cuò),查看其地址為0x00000000(肯定不對),如果減小分配空間,如200*sizeof
    發(fā)表于 03-16 10:05

    C語言內(nèi)存運(yùn)行時(shí)不同變量是怎樣分配的

    C語言內(nèi)存運(yùn)行時(shí)不同變量是怎樣分配的?怎樣驗(yàn)證C語言編譯后的內(nèi)存地址分配是否合理?
    發(fā)表于 02-25 06:37

    請問單片機(jī)運(yùn)行時(shí)內(nèi)存是如何分配的?

    請問單片機(jī)運(yùn)行時(shí)內(nèi)存是如何分配的? 是在鏈接腳本中人工定義?還是編譯器根據(jù)某種算法自動分配?
    發(fā)表于 09-27 08:16

    java線程內(nèi)存模型

    一、Java內(nèi)存模型 按照官方的說法:Java 虛擬機(jī)具有一個(gè)堆,堆是運(yùn)行時(shí)數(shù)據(jù)區(qū)域,所有類實(shí)例和數(shù)組的
    發(fā)表于 09-27 10:55 ?0次下載
    <b class='flag-5'>java</b>線程<b class='flag-5'>內(nèi)存</b>模型

    Java內(nèi)存模型及原理分析

    一、Java內(nèi)存模型 按照官方的說法:Java 虛擬機(jī)具有一個(gè)堆,堆是運(yùn)行時(shí)數(shù)據(jù)區(qū)域,所有類實(shí)例和數(shù)組的
    發(fā)表于 09-28 11:49 ?0次下載
    <b class='flag-5'>Java</b><b class='flag-5'>內(nèi)存</b>模型及原理分析

    利用StopWatch監(jiān)控Java代碼運(yùn)行時(shí)間和分析性能

    利用StopWatch監(jiān)控Java代碼運(yùn)行時(shí)間和分析性能。
    的頭像 發(fā)表于 07-21 16:51 ?2759次閱讀

    Java運(yùn)行時(shí)內(nèi)存區(qū)域硬件內(nèi)存關(guān)系1

    在上一篇文章中,我們了解了計(jì)算機(jī)由于各個(gè)硬件的讀取速度之間的巨大差距,和充分利用CPU的性能的手段方法,及其所帶來的一系列問題: 1. 為了充分壓榨CPU的性能, **CPU 會對指令亂序執(zhí)行或者語言的編譯器會指令重排** ,讓CPU一直工作不停歇,但同時(shí)會導(dǎo)致`有序性問題`。
    的頭像 發(fā)表于 02-09 14:41 ?359次閱讀

    JVM運(yùn)行時(shí)數(shù)據(jù)區(qū)之堆內(nèi)存

    說一下 JVM 運(yùn)行時(shí)數(shù)據(jù)區(qū)吧,都有哪些區(qū)?分別是干什么的?
    的頭像 發(fā)表于 08-19 14:35 ?648次閱讀
    JVM<b class='flag-5'>運(yùn)行時(shí)</b>數(shù)據(jù)區(qū)之堆<b class='flag-5'>內(nèi)存</b>

    如何查看java程序的內(nèi)存分布

    要查看Java程序的內(nèi)存分布,首先需要了解Java程序運(yùn)行時(shí)內(nèi)存模型。 Java程序的
    的頭像 發(fā)表于 11-23 14:47 ?987次閱讀

    jvm內(nèi)存模型和內(nèi)存結(jié)構(gòu)

    內(nèi)存模型是指Java程序在運(yùn)行時(shí),JVM對內(nèi)存空間的組織和管理方式。它包括了線程私有的部分和線程共享的部分。 線程私有部分 線程私有部分主要包含了棧(Stack)和程序計(jì)數(shù)器(Prog
    的頭像 發(fā)表于 12-05 11:08 ?877次閱讀

    jvm運(yùn)行時(shí)內(nèi)存區(qū)域劃分

    內(nèi)存區(qū)域劃分對于了解Java程序的內(nèi)存使用非常重要,本文將詳細(xì)介紹JVM運(yùn)行時(shí)內(nèi)存
    的頭像 發(fā)表于 12-05 14:08 ?490次閱讀

    jvm管理的內(nèi)存包括哪幾個(gè)運(yùn)行時(shí)數(shù)據(jù)內(nèi)存

    JVM(Java虛擬機(jī))是Java程序的運(yùn)行環(huán)境,它提供了內(nèi)存管理機(jī)制來管理Java程序所需的運(yùn)行時(shí)
    的頭像 發(fā)表于 12-05 14:09 ?512次閱讀

    jvm內(nèi)存區(qū)域中,哪一塊是屬于線程共享

    是如何劃分的。JVM內(nèi)存區(qū)域主要分為以下幾個(gè)部分:程序計(jì)數(shù)器、Java虛擬機(jī)棧、本地方法棧、堆、方法區(qū)和運(yùn)行時(shí)常量池。其中,程序計(jì)數(shù)器、Java
    的頭像 發(fā)表于 12-05 14:14 ?1284次閱讀

    java虛擬機(jī)內(nèi)存包括遠(yuǎn)空間內(nèi)存

    Java虛擬機(jī)(JVM)內(nèi)存Java程序執(zhí)行時(shí)所使用的內(nèi)存空間的總稱,包括了Java堆、方法區(qū)
    的頭像 發(fā)表于 12-05 14:15 ?367次閱讀