首页 > 编程知识 正文

内存屏障,java内存模型解决了什么问题

时间:2023-05-05 22:19:57 阅读:42806 作者:2992

主存储器和工作存储器

关于JMM内存之间的交互主存储器和工作存储器之间的具体交互协议,即变量如何从主存储器复制到工作存储器以及如何从工作存储器同步返回到主存储器的实现的详细信息,Java 实现Java虚拟机时,必须确保以下所述的每个操作都是原子的、不可分离的。 (对于双精度和长整型变量,load、store、read和write操作在某些平台上允许异常。 )

long和double变量的特殊规则Java内存模型需要原子性才能执行8个操作: lock、unlock、read、load、assign、use、store和write,但需要64位数据类型(例如定义了一个宽松的规定,允许虚拟机将非volatile限定的64位数据读写操作分为两次32位操作。 也就是说,让虚拟机自己选择是否保证64位数据类型的load、store、read和write这四个操作的原子性,这被称为“长和双非原子性协定”

如果多个线程共享未声明为volatile的长或双精度变量,并且同时读取和修改它们,则一些线程可能会读取表示“一半变量”的数字,而不是原始值或其他线程的修改值。 但是,以这种方式读取到“一半变量”中的情况非常少见,经过实际测试,目前主流平台商用的64位Java虚拟机不会发生非原子访问,但32位Java虚拟机,例如从JDK 9开始,HotSpot添加了一个名为-XX: AlwaysAtomicAccesses的实验参数,该参数是JEP 188更新Java内存模型的一部分,以限制虚拟机对所有数据类型的原子访问另一方面,关于双精度型,在现代的中央处理装置中包含用于处理浮点数据的浮点运算器(FPU(floatingpointunit,fpu ) ),为了处理单精度、双精度的浮点数据,即使在32位虚拟机中通常也是非笔者认为,在实际开发中,只要没有线程冲突(可以明确知道该数据),在编写代码时就不必特意将为此原因使用的长整型和双精度变量声明为volatile。

lock (锁定) :作用于主存储器的变量,将一个变量识别为一个线程独占的状态。 unlock :作用于主存储器的变量。 释放锁定的变量,以便可以在其他线程上锁定释放的变量。 read :作用于主内存的变量。 将变量值从主存储器传输到线程的工作存储器,用于后续的load动作。 作用于load (加载)工作存储器的变量,将通过read操作从主存储器获得的变量值放入工作存储器的变量副本中。 use (使用) :作用于工作存储器的变量。 将工作内存中的变量值传递给执行引擎,并在虚拟机遇到需要使用变量值的字节码指令时执行。 作用于assign (赋值)工作存储器的变量。 它将从执行引擎接收的值分配给工作内存变量,并在虚拟机遇到赋给该变量的字节码指令时执行此操作。 store :作用于工作存储器的变量。 将工作存储器中的变量值传输到主存储器,供后续的白色操作使用。 作用于write (写入)主存储器的变量,将通过store操作从工作存储器获得的变量值放入主存储器的变量中。 如果要将变量从主内存复制到工作内存,请依次执行read和load操作;如果要将变量从工作内存同步返回到主内存,请依次执行store和write操作。 在Java内存模型中,必须按顺序执行上述两个操作,但不需要连续执行。 也就是说,可以在read和load之间、store和write之间插入其他指令。 例如,访问主存储器内的变量a、b时,顺序为read a、read b、load b、load a。 此外,Java内存模型还规定,在执行上述八个基本操作时,必须满足以下规则:

不能单独执行read和load、store和write操作。 也就是说,1个变量从主存储器读取,但工作存储器不接受,或者工作存储器开始写回但主存储器不接受的情况。

不允许线程放弃最近的assign操作。 也就是说,在工作内存中更改变量后,必须将这些更改同步回主内存。

线程没有原因,不能在不执行assign操作的情况下将数据从线程的工作内存同步到主内存。

新变量只能在主存储器中“诞生”,不能在工作存储器中直接使用未初始化的变量(load或assign )。 也就是说,在对变量执行use、store操作之前,必须执行assign和load操作。

一个变量在同一时间只允许一个线程执行锁定操作,但可以在同一线程中多次重复执行锁定操作。 如果多次锁定,则只有在执行相同次数的unlock操作时变量才会解锁。

对变量执行lock操作时,工作存储器中此变量的值为空。

在执行引擎使用这个变量前,需要重新执行load或assign操作以初始化变量的值。

如果一个变量事先没有被lock操作锁定,那就不允许对它执行unlock操作,也不允许去unlock一个 被其他线程锁定的变量。

对一个变量执行unlock操作之前,必须先把此变量同步回主内存中(执行store、write操作)。

       这8种内存访问操作以及上述规则限定,再加上稍后会介绍的专门针对volatile的一些特殊规定,就已经能准确地描述出Java程序中哪些内存访问操作在并发下才是安全的。这种定义相当严谨,但也是极为烦琐,实践起来更是无比麻烦。可能部分读者阅读到这里已经对多线程开发产生恐惧感了,后来 Java设计团队大概也意识到了这个问题,将Java内存模型的操作简化为read、write、lock和unlock四种,但这只是语言描述上的等价化简,Java内存模型的基础设计并未改变,即使是这四操作种,对于普通用户来说阅读使用起来仍然并不方便。不过读者对此无须过分担忧,除了进行虚拟机开发的团队外,大概没有其他开发人员会以这种方式来思考并发问题,我们只需要理解Java内存模型的定义即可。

volatile

三重功效:

64位写入的原子性、内存可见性禁止重排序(实现有序性)。 happens-before

        介绍这种定义的一个等效判断原则——先行发生原则(Happens-Before),用来确定一个操作在并发环境下 是否安全的。

happen-before只确保如果A在B之前执行,则A的执行结果必须对B可见。 单线程:as-if-serial对volatile变量的写,happen-before于后续对这个变量的读。 --编译器和cpu不能重排序对synchronized的解锁,happen-before于后续对这个锁的加锁。对final变量的写,happen-before于final域对象的读,happen-before于后续对final变量的读。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。