Java内存模型基础(JMM)
创始人
2024-03-17 18:19:22
0次

前言 

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

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

 

相关内容

热门资讯

前端-session、jwt 目录:   (1)session (2&#x...
linux入门---制作进度条 了解缓冲区 我们首先来看看下面的操作: 我们首先创建了一个文件并在这个文件里面添加了...
关于测试,我发现了哪些新大陆 关于测试 平常也只是听说过一些关于测试的术语,但并没有使用过测试工具。偶然看到编程老师...
前缀和与对数器与二分法 1. 前缀和 假设有一个数组,我们想大量频繁的去访问L到R这个区间的和,...
nodejs:本地安装nvm实... 一、背景-使用不同版本node的原因 vue3+ts、nuxt3版本,node...
JAVA集合知识整理 Java集合知识整理 HashMap相关 HashMap的底层数据结构:jdk1.8之...
无刷直流电机介绍及单片机控制实... 无刷直流电机介绍及单片机控制实例前言基本概念优势与劣势使用寿命基本结构使用单片机控制实例电子调速器&...
fwdiary(2) dp2 1.传纸条  AcWing 275. 传纸条 - AcWing 走两条路,走一条最大的...
常用的DOS命令 常用的DOS命令 DOS(Disk Operating System,磁...
<C++> 类和对象(下) 1.const成员函数将const修饰的“成员函数”称之为const成员函数,cons...