• Java内存模型基础(JMM)


    前言 

            在并发编程中,一般需要处理两个关键问题:线程通信、线程同步,然而线程通信一般的方式有:共享内存、消息传递。

            对于共享内存的通信方式,线程同步是显示进行的,程序员必须指定某个方法或者某个代码段需要在线程间互斥进行。对于消息传递的通信方式,线程同步是隐式进行的,因为消息的接收一定在发送之后。Java采用的是共享内存的通信方式

    基于java是采用共享内存的线程通信方式 ,因此我们有必要研究Java的内存模型,防止被莫名其妙的内存可见性问题困扰。

    JMM模型

    从抽象的角度来看,线程之间共享的变量存储在主内存中,每个线程都有一个私有的本地内存,存储着该线程读取/修改的变量的副本。本地内存是JMM的一个抽象的概念

     从上图可以看出,线程A和线程B通信就要求,线程A先把本地内存更新过的变量更新到主内存中,然后线程B去主内存读取线程A更新了的变量。整体上来看,JMM就是通过控制主内存和每个线程的本地内存之间的交互,来为Java程序员提供内存可见性保证。

    指令序列重排序

    在执行程序,为了提高性能,编译器和处理器常常会对指令做重排序,重排序分为以下三类:

    1. 编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句
    2. 指令级并行的重排序。将多条指令重叠执行,如果不存在数据依赖性,处理器可以改变语句对应的机器指令的执行顺序
    3. 内存系统的重排序。由于处理器使用了缓存区,这使得加载操作和存储操作看上去可能是杂乱进行的。

    上述1属于编译器重排序,2和3属于处理器重排序,JMM在处理重排序的规则的时候,会要求Java编译器在生成指令序列时,插入特定类型的内存屏障指令,通过内存屏障指令来禁止特定类型的处理器重排序。

    现在的处理器都会用到高速缓冲区存储数据,这样可以有效避免由于处理器停顿下来等待向内存写入的数据而产生的延迟,同时也可以批量刷新缓存提高效率,减少对内存线的占用。在多核处理器盛行的时代,每个处理器都有相应的高速缓冲区,这样同样也为并发编程带来了挑战,与上面JMM模型一样,处理器也存在缓存和内存数据不一致的情况。

    这里的关是,由于写冲区仅对自己的理器可,它会理器行内存操作的 顺序可能会与内存实际的操作序不一致。由于代的理器都会使用写冲区,因此代的理器都会允许对-操作进行重排序

    常见处理器允许的重排序类型如下:

     其中N代表不允许两个操作重排序,Y代表允许两个操作重排序。

    Load:装载数据(从内存中读取数据)

    Store:存储数据(存储数据到内存)

    内存屏障

    为了保证内存可见性,Java编译器在生成指令的适当位置会插入内存屏障指令来禁止特定类型的重排序。

    内存屏障类型:

    StoreLoad Barriers 是一个 全能型 的屏障,它同 具有其他 3 个屏障的效果。 代的多
    理器大多支持 屏障(其他 型的屏障不一定被所有 理器支持)。 屏障开 会很昂
    ,因 当前 理器通常要把写 冲区中的数据全部刷新到内存中( Buffer Fully Flush )。

  • 相关阅读:
    springboot+thymeleaf 封装自定义解析json字符串标签方法
    连锁门店订货补货 集中采购信息化解决方案
    自研、好用、够快、稳定、代码可读性强的ORM
    C. Swap Letters
    美团一面,面试官让介绍AQS原理并手写一个同步器,直接凉了
    Prophet在R语言中进行时间序列数据预测
    Mybatis中解决数据库表和实体类字段名不一致的方式
    Java适配器模式 - 灵活应对不匹配的接口
    您的企业内容管理(ECM)系统对敏感信息的保护程度如何?
    PT_数字特征_矩协方差相关系数
  • 原文地址:https://blog.csdn.net/huangwei2014/article/details/128173421