0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Java运行时内存区域与硬件内存的关系2

jf_78858299 来源:小牛呼噜噜 作者:小牛呼噜噜 2023-02-09 14:41 次阅读

线程间通信

线程间的通信一般有两种方式进行,一是通过消息传递,二是共享内存Java 线程间的通信采用的是共享内存方式,JMM 为共享变量提供了线程间的保障。如果两个线程都对一个共享变量进行操作,共享变量初始值为 1,每个线程都变量进行加 1,预期共享变量的值为 3。在 JMM 规范下会有一系列的操作。我们直接来看下图:

在多线程的情况下,对主内存中的共享变量进行操作可能发生线程安全问题,比如:线程 1 和线程 2 同时对同一个共享变量进行操作,执行+1操作,线程 1 、线程2 读取的共享变量是否是彼此修改前还是修改后的值呢,这个是无法确定的,这种情况和CPU的高速缓存与内存之间的问题非常相似

如何实现主内存与工作内存的变量同步,为了更好的控制主内存和本地内存的交互,Java 内存模型定义了八种操作来实现:

  • lock:锁定。作用于主内存的变量,把一个变量标识为一条线程独占状态。
  • unlock:解锁。作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
  • read:读取。作用于主内存变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用
  • load:载入。作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。工作内存即本地内存
  • use:使用。作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。
  • assign:赋值。作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
  • store:存储。作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作。
  • write:写入。作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存的变量中。

重温Java 并发三大特性

原子性

原子性:即一个或者多个操作作为一个整体,要么全部执行,要么都不执行,并且操作在执行过程中不会被线程调度机制打断;而且这种操作一旦开始,就一直运行到结束,中间不会有任何上下文切换(context switch) 比如:

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

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

语句1大家一幕了然,语句2却许多人容易犯迷糊,i++ 其实可以分为3步:

  1. i 被从局部变量表(内存)取出,
  2. 压入操作栈(寄存器),操作栈中自增
  3. 使用栈顶值更新局部变量表(寄存器更新写入内存)

执行上述3个步骤的时候是可以进行线程切换的,或者说是可以被另其他线程的 这3 步打断的,因此语句2不是一个原子性操作

在 Java 中,可以借助synchronized 、各种 Lock 以及各种原子类实现原子性。synchronized 和各种Lock是通过保证任一时刻只有一个线程访问该代码块,因此可以保证其原子性。各种原子类是利用CAS (compare and swap)操作(可能也会用到 volatile或者final关键字)来保证原子操作。

可见性

可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看到修改的值。我们来看一个例子:

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() + ",开始执行.....");
        int i = 0;
        while (flag) {
            i++;
        }
        System.out.println(Thread.currentThread().getName() + ",结束循环");
    }

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

        // 线程threadA模拟数据加载场景
        Thread threadA = new Thread(() -> test.load(), "threadA");
        threadA.start();

        // 让threadA执行一会儿
        Thread.sleep(1000);
        // 线程threadB 修改 共享变量flag
        Thread threadB = new Thread(() -> test.change(), "threadB");
        threadB.start();

    }
}

threadA 负责循环,threadB负责修改 共享变量flag,如果flag=false时,threadA 会结束循环,但是上面的例子会死循环。原因是threadA无法立即读取到共享变量flag修改后的值。我们只需 private volatile boolean flag = true;加上volatile关键字threadA就可以立即退出循环了。

Java中的volatile关键字提供了一个功能,那就是被其修饰的变量在被修改后可以立即同步到主内存,被其修饰的变量在每次是用之前都从主内存刷新。

因此,可以使用volatile来保证多线程操作时变量的可见性。除了volatile,Java中的synchronizedfinal两个关键字 以及各种 Lock也可以实现可见性。

有序性

有序性:即程序执行的顺序按照代码的先后顺序执行。

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

但由于指令重排序问题,代码的执行顺序未必就是编写代码时候的顺序。语句可能的执行顺序如下:

  1. 语句1 语句2
  2. 语句2 语句1

指令重排对于非原子性的操作,在不影响最终结果的情况下,其拆分成的原子操作可能会被重新排列执行顺序。 指令重排不会影响单线程的执行结果,但是会影响多线程并发执行的结果正确性 。在Java 中,可以通过volatile关键字来禁止指令进行重排序优化,详情可见:https://mp.weixin.qq.com/s/TyiCfVMeeDwa-2hd9N9XJQ。也可以使用synchronized关键字保证同一时刻只允许一条线程访问程序块。


参考资料

《java并发编程实战》

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

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

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

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • JAVA
    +关注

    关注

    19

    文章

    2959

    浏览量

    104555
  • 编译器
    +关注

    关注

    1

    文章

    1618

    浏览量

    49054
  • JVM
    JVM
    +关注

    关注

    0

    文章

    157

    浏览量

    12209
收藏 人收藏

    评论

    相关推荐

    Labview 运行时内存增加

    的dll,用库函数直接调用这个dll后,Labview运行时所占的内存基本上保持在0.9 M左右,不会卡死了。附件里是那个网友上传的dll,大家可以下载后将.jpg改为.dll
    发表于 05-19 14:38

    C6748内存分配运行时出错

    因为需要对大量数据进行处理,我设置一个指针,内存分配1000*sizeof(float),运行时出错,查看其地址为0x00000000(肯定不对),如果减小分配空间,如200*sizeof
    发表于 03-16 10:05

    C语言内存运行时不同变量是怎样分配的

    C语言内存运行时不同变量是怎样分配的?怎样验证C语言编译后的内存地址分配是否合理?
    发表于 02-25 06:37

    请问单片机运行时内存是如何分配的?

    请问单片机运行时内存是如何分配的? 是在链接脚本中人工定义?还是编译器根据某种算法自动分配?
    发表于 09-27 08:16

    java线程内存模型

    一、Java内存模型 按照官方的说法:Java 虚拟机具有一个堆,堆是运行时数据区域,所有类实例和数组的
    发表于 09-27 10:55 0次下载
    <b class='flag-5'>java</b>线程<b class='flag-5'>内存</b>模型

    Java内存模型及原理分析

    一、Java内存模型 按照官方的说法:Java 虚拟机具有一个堆,堆是运行时数据区域,所有类实例和数组的
    发表于 09-28 11:49 0次下载
    <b class='flag-5'>Java</b><b class='flag-5'>内存</b>模型及原理分析

    利用StopWatch监控Java代码运行时间和分析性能

    利用StopWatch监控Java代码运行时间和分析性能。
    的头像 发表于 07-21 16:51 2785次阅读

    Java运行时内存区域硬件内存关系1

    在上一篇文章中,我们了解了计算机由于各个硬件的读取速度之间的巨大差距,和充分利用CPU的性能的手段方法,及其所带来的一系列问题: 1. 为了充分压榨CPU的性能, **CPU 会对指令乱序执行或者语言的编译器会指令重排** ,让CPU一直工作不停歇,但同时会导致`有序性问题`。
    的头像 发表于 02-09 14:41 365次阅读

    JVM运行时数据区之堆内存

    说一下 JVM 运行时数据区吧,都有哪些区?分别是干什么的?
    的头像 发表于 08-19 14:35 659次阅读
    JVM<b class='flag-5'>运行时</b>数据区之堆<b class='flag-5'>内存</b>

    如何查看java程序的内存分布

    要查看Java程序的内存分布,首先需要了解Java程序运行时内存模型。 Java程序的
    的头像 发表于 11-23 14:47 1001次阅读

    jvm内存模型和内存结构

    内存模型是指Java程序在运行时,JVM对内存空间的组织和管理方式。它包括了线程私有的部分和线程共享的部分。 线程私有部分 线程私有部分主要包含了栈(Stack)和程序计数器(Prog
    的头像 发表于 12-05 11:08 897次阅读

    jvm运行时内存区域划分

    内存区域划分对于了解Java程序的内存使用非常重要,本文将详细介绍JVM运行时内存
    的头像 发表于 12-05 14:08 508次阅读

    jvm管理的内存包括哪几个运行时数据内存

    JVM(Java虚拟机)是Java程序的运行环境,它提供了内存管理机制来管理Java程序所需的运行时
    的头像 发表于 12-05 14:09 529次阅读

    jvm内存区域中,哪一块是属于线程共享

    是如何划分的。JVM内存区域主要分为以下几个部分:程序计数器、Java虚拟机栈、本地方法栈、堆、方法区和运行时常量池。其中,程序计数器、Java
    的头像 发表于 12-05 14:14 1326次阅读

    java虚拟机内存包括远空间内存

    Java虚拟机(JVM)内存Java程序执行时所使用的内存空间的总称,包括了Java堆、方法区
    的头像 发表于 12-05 14:15 380次阅读