区分什么是Java内存模型(JMM)和 JVM运行时数据区

文章目录

  • 一、概念区分
    • 1、什么是内存模型?什么是(内存区域)运行时数据区?
    • 2、为什么要有Java内存模型?
      • 2.1、硬件的效率与一致性
      • 2.2、 CPU和缓存的一致性
        • 2.2.1、为什么需要CPU cache?
        • 2.2.2、三级缓存(L1、L2、L3)
      • 2.3、 乱序执行优化
  • 二、JMM- Java内存模型
    • 2.1、内存模型组成及抽象示意图
    • 2.2、内存交互的基本操作
    • 2.3、Java内存模型的运行规则
      • 1、八大操作的同步规则
      • 2、内存交互的3个特性
        • 原子性、可见性、有序性
        • volatile的可见性、有序性
        • synchronized的原子性、可见性、有序性
      • 3、重排序
      • 4、JMM的重排序屏障
        • 数据依赖性
        • as-if-serial语义
        • 重排序对多线程的影响
      • 5、happens-before 先行发生原则
      • final变量的特殊规则
      • long 、doube类型的变量的特殊规则
  • 三、JVM运行时数据区(Java内存区域)

一、概念区分

1、什么是内存模型?什么是(内存区域)运行时数据区?

Java的内存区域和内存模型是不一样的东西,内存区域是指 JVM 运行时将数据分区域存储,强调对内存空间的划分

而内存模型(Java Memory Model,简称 JMM )是定义了线程和主内存之间的抽象关系,即 JMM 定义了 JVM 在计算机内存(RAM)中的工作方式,如果我们要想深入了解Java并发编程,就要先理解好Java内存模型。

2、为什么要有Java内存模型?

  • 在现代多核处理器中,每个处理器都有自己的缓存,需要定期的与主内存进行协调。
  • 想要确保每个处理器在任意时刻知道其他处理器正在进行的工作,将需要很大的开销,且通常是没必要的。

2.1、硬件的效率与一致性

1、 由于计算机的存储设备与处理器的运算能力之间有几个数量级的差距,所以现代计算机系统都不得不加入一层读写速度尽可能接近处理器运算速度的高速缓存(cache)来作为内存与处理器之间的缓冲:将运算需要使用到的数据复制到缓存中,让运算能快速进行,当运算结束后再从缓存同步回内存之中,这样一来处理器就无需等待缓慢的内存读写了。
2、基于高速缓存的存储交互很好地解决了处理器与内存的速度矛盾,但是引入了一个新的问题:缓存一致性(Cache Coherence)。在多处理器系统中,每个处理器都有自己的高速缓存,而他们又共享同一主存。如下图所示:
在这里插入图片描述
多个处理器运算任务都涉及同一块主存,需要一种协议可以保障数据的一致性,这类协议有MSI、MESI、MOSI及Dragon Protocol等。Java虚拟机内存模型中定义的内存访问操作与硬件的缓存访问操作是具有可比性的。(看下面第二点介绍)

3、除此之外,为了使得处理器内部的运算单元能尽可能被充分利用,处理器可能会对输入代码进行乱序执行(Out-Of-Order Execution)优化,处理器会在计算之后将对乱序执行的代码进行结果重组,保证结果准确性。与处理器的乱序执行优化类似,Java虚拟机的即时编译器中也有类似的指令重排序(Instruction Recorder)优化。看下面第三点介绍。

2.2、 CPU和缓存的一致性

2.2.1、为什么需要CPU cache?

因为CPU的频率太快了,快到主存跟不上,这样在处理器时钟周期内,CPU常常需要等待主存,浪费资源。CPU往往需要重复处理相同的数据、重复执行相同的指令,如果这部分数据、指令CPU能在CPU缓存中找到,CPU就不需要从内存或硬盘中再读取数据、指令,从而减少了整机的响应时间,所以cache的出现,是为了缓解CPU和内存之间速度的不匹配问题
(结构:cpu -> cache -> memory)
在这里插入图片描述
在程序执行的过程中就变成了:

当程序在运行过程中,会将运算需要的数据从主存复制一份到CPU的高速缓存当中,
那么CPU进行计算时就可以直接从它的高速缓存读取数据和向其中写入数据,
当运算结束之后,再将高速缓存中的数据刷新到主存当中。

在Intel官网上产品-处理器界面内对缓存的定义为:CPU高速缓存是处理器上的一个快速记忆区域。英特尔智能高速缓存(SmartCache)是指可让所有内核动态共享最后一级高速缓存的架构。这里就提及到了最后一级高速缓存的概念,即为CPU缓存中的L3(三级缓存),那么我们继续来解释一下什么叫三级缓存,分别又是指哪三级缓存。

在这里插入图片描述

2.2.2、三级缓存(L1、L2、L3)

  1. 三级缓存(L1一级缓存、L2二级缓存、L3三级缓存)都是集成在CPU内的缓存
  2. 它们的作用都是作为CPU与主内存之间的高速数据缓冲区
  3. L1最靠近CPU核心,L2其次,L3再次
    运行速度方面:L1最快、L2次快、L3最慢
    容量大小方面:L1最小、L2较大、L3最大
  4. CPU会先在最快的L1中寻找需要的数据,找不到再去找次快的L2,还找不到再去找L3,L3都没有那就只能去内存找了。
  5. 单核CPU只含有一套L1,L2,L3缓存;如果CPU含有多个核心,即多核CPU,则每个核心都含有一套L1(甚至和L2)缓存,而共享L3(或者和L2)缓存。

单CPU双核的缓存结构
在这里插入图片描述

在单线程环境下,cpu核心的缓存只被一个线程访问。缓存独占,不会出现访问冲突等问题
在多线程场景下,在CPU和主存之间增加缓存,就可能存在缓存一致性问题,也就是说,在多核CPU中,每个核的自己的缓存中,关于同一个数据的缓存内容可能不一致,这也就是我们上面提到的缓存一致性的问题
在这里插入图片描述

2.3、 乱序执行优化

从java源码到最终实际执行的指令序列,会经历下面3种重排序:
在这里插入图片描述
举个重排序的例子:
在这里插入图片描述

  • a=10,b=a 这一组 b依赖a,不会重排序
  • a=10,b=50 这一组 a和b 没有关系,那么就有可能被重排序执行 b=50,a=10
    cpu和编译器为了提高程序的执行效率会按照一定的规则允许指令优化,不影响单线程程序执行结果,但是多线程就会影响程序结果

二、JMM- Java内存模型

在这里插入图片描述

Java内存模型即Java Memory Model,简称JMM。JMM定义了Java 虚拟机(JVM)在计算机内存(RAM)中的工作方式。JVM是整个计算机虚拟模型,所以JMM是隶属于JVM的。

Java内存模型(Java Memory Model ,JMM)就是一种符合内存模型规范的,屏蔽了各种硬件和操作系统的访问差异的,保证了Java程序在各种平台下对内存的访问都能保证效果一致的机制及规范。可以避免像c++等直接使用物理硬件和操作系统的内存模型在不同操作系统和硬件平台下表现不同,比如有些c/c++程序可能在windows平台运行正常,而在linux平台却运行有问题。这即使得Java程序能够 “一次编写,到处运行”

Java内存模型是共享内存的并发模型,线程之间主要通过读-写共享变量(堆内存中的实例域,静态域和数组元素)来完成隐式通信。

Java 内存模型(JMM)控制 Java 线程之间的通信,决定一个线程对共享变量的写入何时对另一个线程可见。

2.1、内存模型组成及抽象示意图

主内存:Java内存模型规定了所有变量都存储在主内存(Main Memory)中(此处的主内存与介绍物理硬件的主内存名字一样,两者可以互相类比,但此处仅是虚拟机内存的一部分)。

工作内存:每条线程都有自己的工作内存(Working Memory,又称本地内存,可与前面介绍的处理器高速缓存类比),线程的工作内存中保存了该线程使用到的变量的主内存中的共享变量的副本拷贝。工作内存是 JMM 的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。

在这里插入图片描述
从上图来看,线程A与线程B之间如要通信的话,必须要经历下面2个步骤:

  1. 线程A把本地内存A中更新过的共享变量刷新到主内存中去。
  2. 线程B到主内存中去读取线程A之前已更新过的共享变量。

拿图举例:
在这里插入图片描述
这样才完成了上面的线程对下面线程变量的访问。

2.2、内存交互的基本操作

关于主内存与工作内存之间的具体交互协议,即一个变量如何从主内存拷贝到工作内存、如何从工作内存同步回主内存之类的实现细节,Java内存模型中定义了下面介绍8种操作来完成。

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

2.3、Java内存模型的运行规则

1、八大操作的同步规则

JMM在执行前面介绍8种基本操作时,为了保证内存间数据一致性,JMM中规定需要满足以下规则:

  • 规则1:如果要把一个变量从主内存中复制到工作内存,就需要按顺序的执行 read 和 load 操作,如果把变量从工作内存中同步回主内存中,就要按顺序的执行 store 和 write 操作。但 Java 内存模型只要求上述操作必须按顺序执行,而没有保证必须是连续执行。

  • 规则2:不允许 read 和 load、store 和 write 操作之一单独出现。

  • 规则3:不允许一个线程丢弃它的最近 assign 的操作,即变量在工作内存中改变了之后必须同步到主内存中。

  • 规则4:不允许一个线程无原因的(没有发生过任何 assign 操作)把数据从工作内存同步回主内存中。

  • 规则5:一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化(load 或 assign )的变量。即就是对一个变量实施 use 和 store 操作之前,必须先执行过了 load 或 assign 操作。

  • 规则6:一个变量在同一个时刻只允许一条线程对其进行 lock 操作,但 lock 操作可以被同一条线程重复执行多次,多次执行 lock 后,只有执行相同次数的 unlock 操作,变量才会被解锁。所以 lock 和 unlock 必须成对出现。

  • 规则7:如果对一个变量执行 lock 操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前需要重新执行 load 或 assign 操作初始化变量的值。

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

  • 规则9:对一个变量执行 unlock 操作之前,必须先把此变量同步到主内存中(执行 store 和 write 操作)

看起来这些规则有些繁琐,其实也不难理解:

规则1、规则2 工作内存中的共享变量作为主内存的副本,主内存变量的值同步到工作内存需要read和load一起使用,工作内存中的变量的值同步回主内存需要store和write一起使用,这2组操作各自都是是一个固定的有序搭配,不允许单独出现。

规则3、规则4 由于工作内存中的共享变量是主内存的副本,为保证数据一致性,当工作内存中的变量被字节码引擎重新赋值,必须同步回主内存。如果工作内存的变量没有被更新,不允许无原因同步回主内存。

规则5 由于工作内存中的共享变量是主内存的副本,必须从主内存诞生。

规则6、7、8、9 为了并发情况下安全使用变量,线程可以基于lock操作独占主内存中的变量,其他线程不允许使用或unlock该变量,直到变量被线程unlock。

2、内存交互的3个特性

Java内存模型是围绕着在并发过程中如何处理这3个特性来建立的,即原子性、有序性、可见性。它是如何保证这三种特性的呢?

原子性、可见性、有序性

原子性

即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。即使在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程所干扰。
JMM保证的原子性变量操作包括read、load、assign、use、store、write,而long、double非原子协定导致的非原子性操作基本可以忽略。如果需要对更大范围的代码实行原子性操作,则需要JMM提供的lock、unlock、synchronized等来保证。

可见性

是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
JMM在变量修改后将新值同步回主内存,依赖主内存作为媒介,在变量被线程读取前从内存刷新变量新值,保证变量的可见性。普通变量和volatile变量都是如此,只不过volatile的特殊规则保证了这种可见性是立即得知的,而普通变量并不具备这种严格的可见性(不知道何时写到主存,线程读取到可能为旧值)。除了volatile外,synchronized和final也能保证可见性。

有序性
JMM的有序性规则表现在以下两种场景: 线程内和线程间

  • 线程内 :从某个线程的角度看方法的执行,指令会按照一种叫“串行”(as-if-serial)的方式执行,此种方式已经应用于顺序编程语言。
  • 线程间 :这个线程“观察”到其他线程并发地执行非同步的代码时,由于指令重排序优化,任何代码都有可能交叉执行。唯一起作用的约束是:对于同步方法,同步块(synchronized关键字修饰)以及volatile字段的操作仍维持相对有序。

volatile的可见性、有序性

可见性
保证了不同线程对该变量操作的内存可见性。
当一个共享变量被volatile修饰时,它会保证当前线程修改的值立即被更新到主存。
其他线程工作内存中的变量会强制立即失效,当其他线程需要读取时,会去主内存中读取最新值。

但是如果多个线程同时把更新后的变量值同时刷新回主内存,可能导致得到的值不是预期结果:
举个例子:定义volatile int count = 0,2个线程同时执行count++操作,每个线程都执行500次,最终结果小于1000,原因是每个线程执行count++需要以下3个步骤:

  • 步骤1 线程从主内存读取最新的count的值
  • 步骤2 执行引擎把count值加1,并赋值给线程工作内存
  • 步骤3 线程工作内存把count值保存到主内存
    有可能某一时刻2个线程在步骤1读取到的值都是100,执行完步骤2得到的值都是101,最后刷新了2次101保存到主内存

有序性
原理:volatile的可见性和有序性都是通过加入内存屏障来实现
会在写之后加入一条store屏障指令,将本地内存中值刷新到主内存。
会在读之前加入一条load屏障指令,从主内存中读取共享变量。

具体一点:

当程序执行到 volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行;

在进行指令优化时,不能将在对 volatile 变量访问的语句放在其后面执行,也不能把 volatile 变量后面的语句放到其前面执行。

在这里插入图片描述

synchronized的原子性、可见性、有序性

synchronized能够在同一时刻最多只有一个线程执行该代码,已达到并发线程同步安全的效果,

synchronized能保证线程的原子性,可见性和有序性

synchronized能保证线程的原子性原理:synchronized保证只有一个线程拿到锁,即在同一时刻只有一个线程执行同步代码块或同步方法。

synchronized能保证可见性的原理:执行synchronized的方法对应的是lock原子操作会刷新线程工作内存中的共享变量,从而使其得到最新值

synchronized能保证有序性原理:加入synchronized后,依然会发生重排序,只不过,同步代码块,可以保证只有一个线程执行同步代码块中代码,从而保证有序性

3、重排序

在执行程序时为了提高性能,编译器和处理器经常会对指令进行重排序。从硬件架构上来说,指令重排序是指CPU采用了允许将多条指令不按照程序规定的顺序,分开发送给各个相应电路单元处理,而不是指令任意重排。重排序分成三种类型:

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

在这里插入图片描述

4、JMM的重排序屏障

从Java源代码到最终实际执行的指令序列,会经过三种重排序。但是,为了保证内存的可见性,Java编译器在生成指令序列的适当位置会插入内存屏障指令来禁止特定类型的处理器重排序。对于编译器的重排序,JMM会根据重排序规则禁止特定类型的编译器重排序;对于处理器重排序,JMM会插入特定类型的内存屏障,通过内存的屏障指令禁止特定类型的处理器重排序。这里讨论JMM对处理器的重排序,为了更深理解JMM对处理器重排序的处理,先来认识一下常见处理器的重排序规则:
在这里插入图片描述
其中的N标识处理器不允许两个操作进行重排序,Y表示允许。其中Load-Load表示读-读操作、Load-Store表示读-写操作、Store-Store表示写-写操作、Store-Load表示写-读操作。可以看出:常见处理器对写-读操作都是允许重排序的,并且常见的处理器都不允许对存在数据依赖的操作进行重排序(对应上面数据转换那一列,都是N,所以处理器不允许这种重排序)。

那么这个结论对我们有什么作用呢?比如第一点:处理器允许写-读操作两者之间的重排序,那么在并发编程中读线程读到可能是一个未被初始化或者是一个NULL等,出现不可预知的错误,基于这点,JMM会在适当的位置插入内存屏障指令来禁止特定类型的处理器的重排序。内存屏障指令一共有4类:

LoadLoad屏障
对于这样的语句 Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。

StoreStore屏障
对于这样的语句 Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。

LoadStore屏障
对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被执行前,保证Load1要读取的数据被读取完毕。

StoreLoad屏障
对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。它的开销是四种屏障中最大的(冲刷写缓冲器,清空无效化队列)。在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能。

数据依赖性

根据上面的表格,处理器不会对存在数据依赖的操作进行重排序。这里数据依赖的准确定义是:如果两个操作同时访问一个变量,其中一个操作是写操作,此时这两个操作就构成了数据依赖。常见的具有这个特性的如i++、i—。如果改变了具有数据依赖的两个操作的执行顺序,那么最后的执行结果就会被改变。这也是不能进行重排序的原因。例如:

写后读:a = 1; b = a;
写后写:a = 1; a = 2;
读后写:a = b; b = 1;
重排序遵守数据依赖性,编译器和处理器不会改变存在数据依赖关系的两个操作的执行顺序。但是这里所说的数据依赖性仅针对单个处理器中执行的指令序列和单个线程中执行的操作,不同处理器之间和不同线程之间的数据依赖性不被编译器和处理器考虑。

as-if-serial语义

as-if-serial语义的意思指:管怎么重排序(编译器和处理器为了提高并行度),(单线程)程序的执行结果不能被改变。编译器,runtime 和处理器都必须遵守as-if-serial语义。

as-if-serial语义把单线程程序保护了起来,遵守as-if-serial语义的编译器,runtime 和处理器共同为编写单线程程序的程序员创建了一个幻觉:单线程程序是按程序的顺序来执行的。as-if-serial语义使单线程程序员无需担心重排序会干扰他们,也无需担心内存可见性问题。

重排序对多线程的影响

如果代码中存在控制依赖的时候,会影响指令序列执行的并行度(因为高效)。也是为此,编译器和处理器会采用猜测(Speculation)执行来克服控制的相关性。所以重排序破坏了程序顺序规则(该规则是说指令执行顺序与实际代码的执行顺序是一致的,但是处理器和编译器会进行重排序,只要最后的结果不会改变,该重排序就是合理的)。

在单线程程序中,由于as-ifserial语义的存在,对存在控制依赖的操作重排序,不会改变执行结果;但在多线程程序中,对存在控制依赖的操作重排序,可能会改变程序的执行结果。

5、happens-before 先行发生原则

happens-before关系:用于描述下2个操作的内存可见性:如果操作A happens-before 操作B,那么A的结果对B可见
happens-before关系的分析需要分为单线程和多线程的情况:

  • 单线程下的 happens-before
    字节码的先后顺序天然包含happens-before关系:因为单线程内共享一份工作内存,不存在数据一致性的问题。
    在程序控制流路径中靠前的字节码 happens-before 靠后的字节码,即靠前的字节码执行完之后操作结果对靠后的字节码可见。然而,这并不意味着前者一定在后者之前执行。实际上,如果后者不依赖前者的运行结果,那么它们可能会被重排序。

  • 多线程下的 happens-before
    多线程由于每个线程有共享变量的副本,如果没有对共享变量做同步处理,线程1更新执行操作A共享变量的值之后,线程2开始执行操作B,此时操作A产生的结果对操作B不一定可见。

倘若在开发中,仅靠synchronized和volatile来保证顺序性、原子性、可见性。那么编写并发程序会十分麻烦。在Java内存模型中,还提供了happens-before原则来辅助保证程序执行的原子性、可见性、有序性的问题。它是判断数据是否存在竞争,线程是否安全的依据。
Java内存模型实现了下述支持happens-before关系的操作:

  • 程序次序规则
    一个线程内,按照代码顺序,书写在前面的操作 happens-before 书写在后面的操作。
  • 锁定规则
    一个unLock操作 happens-before 后面对同一个锁的lock操作。
  • volatile变量规则
    对一个volatile变量的写操作 happens-before 后面对这个变量的读操作。
  • 传递规则
    如果操作A happens-before 操作B,而操作B又 happens-before 操作C,则可以得出操作A happens-before 操作C。
  • 线程启动规则
    Thread对象的start()方法 happens-before 此线程的每个一个动作。
  • 线程中断规则
    对线程interrupt()方法的调用 happens-before 被中断线程的代码检测到中断事件的发生。
  • 线程终结规则
    线程中所有的操作都 happens-before 线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行。
  • 对象终结规则
    一个对象的初始化完成 happens-before 他的finalize()方法的开始

注意:不同操作时间先后顺序与先行发生原则之间没有关系,二者不能相互推断,衡量并发安全问题不能受到时间顺序的干扰,一切都要以happens-before原则为准

示例代码1:

private int value = 0;public void setValue(int value) {this.value = value;
}public int getValue() {return this.value;
}

对于上面的代码,假设线程A在时间上先调用setValue(1),然后线程B调用getValue()方法,那么线程B收到的返回值一定是1吗?

按照happens-before原则,两个操作不在同一个线程、没有通道锁同步、线程的相关启动、终止和中断以及对象终结和传递性等规则都与此处没有关系,因此这两个操作是不符合happens-before原则的,这里的并发操作是不安全的,返回值并不一定是1。

对于该问题的修复,可以使用lock或者synchronized套用“管程锁定规则”实现先行发生关系;或者将value定义为volatile变量(两个方法的调用都不存在数据依赖性),套用“volatile变量规则”实现先行发生关系。如此一来,就能保证并发安全性。

示例代码2

// 以下操作在同一个线程中
int i = 1;
int j = 2;

上面的代码符合“程序次序规则”,满足先行发生关系,但是第2条语句完全可能由于重排序而被处理器先执行,时间上先于第1条语句。

final变量的特殊规则

我们知道,final成员变量必须在声明的时候初始化或者在构造器中初始化,否则就会报编译错误。
final关键字的可见性是指:被final修饰的字段在声明时或者构造器中,一旦初始化完成,那么在其他线程无须同步就能正确看见final字段的值。这是因为一旦初始化完成,final变量的值立刻回写到主内存

作者:浪里小白龙
链接:https://www.jianshu.com/p/d87e9b6747b0
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

long 、doube类型的变量的特殊规则

Java内存模型要求lock、unlock、read、load、assign、use、store、write这8种操作都具有原子性,但是对于64位的数据类型(long和double),在模型中特别定义相对宽松的规定:允许虚拟机将没有被volatile修饰的64位数据的读写操作分为2次32位的操作来进行。也就是说虚拟机可选择不保证64位数据类型的load、store、read和write这4个操作的原子性。由于这种非原子性,有可能导致其他线程读到同步未完成的“32位的半个变量”的值。

不过实际开发中,Java内存模型强烈建议虚拟机把64位数据的读写实现为具有原子性,目前各种平台下的商用虚拟机都选择把64位数据的读写操作作为原子操作来对待,因此我们在编写代码时一般不需要把用到的long和double变量专门声明为volatile。

三、JVM运行时数据区(Java内存区域)

看这篇


参考文章:
3. 深入理解JVM: Java内存模型JMM
4. JVM(七)JMM内存模型
5. Java内存区域(运行时数据区域) 和 内存模型(JMM)
6. JVM内存模型
7. Java内存模型

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/106393.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Redis知识点总结

概述 Redis诞生于2009年,全称是Remote Dictionarty Server(远程词典服务器) 只支持单线程 非关联:主要指的是表中没有主外键等概念 Redis是一款内存数据库,主要存储键值对类型的数据 基本用法 注意:该操作是在cli中进行的 首…

【MySQL系列】统计函数(count,sum,avg)详解

💐 🌸 🌷 🍀 🌹 🌻 🌺 🍁 🍃 🍂 🌿 🍄🍝 🍛 🍤 📃个人主页 :阿然成长日记 …

【面向大一新生IT技术社群招新啦,不来瞅瞅?】

个人名片: 🐼作者简介:一名大三在校生 🐻‍❄️个人主页:落798. 🐼个人WeChat:落798. 🕊️系列专栏:【零基础学java】 ----- 【重识c语言】 ---- 【计算机网络】—【Spri…

接口测试时遇到接口加密了该如何处理?

对明文编码生成信息摘要,以防止被篡改。比如MD5使用的是Hash算法,无论多长的输入,MD5都会输出长度为128bits的一个串。摘要算法不要秘钥,客户端和服务端采用相同的摘要算法即可针对同一段明文获取一致的密文。 对称加密 对称加密…

设计模式之工厂模式(万字长文)

文章目录 概述工厂模式的优点包括工厂模式有几种主要的变体看一个具体需求使用传统的方式来完成传统的方式的优缺点 简单工厂模式基本介绍使用简单工厂模式简单工厂模式的优缺点优点:缺点: 工厂方法模式看一个新的需求思路 1思路 2工厂方法模式介绍工厂方…

macOS上编译obs-studio

前言 最近基于obs的1个二开程序,需要移植到macOS平台上,由于遇到些问题,本文记录下如何在macOS上配置&编译&运行obs程序完整过程。 下载 首先下载cmake-gui工具,下载CMAKE,选择对应macOS平台的cmake版本&…

【ArcGIS微课1000例】0074:ArcGIS热点分析(Getis-Ord Gi*)---犯罪率热点图

严重声明:本文来自专栏《ArcGIS微课1000例:从点滴到精通》,为CSDN博客专家刘一哥GIS原创,原文及专栏地址为:(https://blog.csdn.net/lucky51222/category_11121281.html),谢绝转载或爬取!!! 文章目录 一、热点分析工具介绍二、ArcGIS热点分析案例1. 普通热点分析2. 加…

【ArcGIS微课1000例】0072:如何生成空间权重矩阵

严重声明:本文来自专栏《ArcGIS微课1000例:从点滴到精通》,为CSDN博客专家刘一哥GIS原创,原文及专栏地址为:(https://blog.csdn.net/lucky51222/category_11121281.html),谢绝转载或爬取!!! 文章目录 一、空间权重矩阵工具介绍二、ArcGIS生成空间权重矩阵三、注意事项…

从零开始配置Jenkins与GitLab集成:一步步实现持续集成

在软件开发中,持续集成是确保高效协作和可靠交付的核心实践。以下是在CentOS上安装配置Jenkins与GitLab集成的详细步骤: 1.安装JDK 解压JDK安装包并设置环境变量: JDK下载网址 Java Downloads | Oracle 台灣 tar zxvf jdk-11.0.5_linux-x64_b…

设计模式三原则

1.1单一职责原则 C 面向对象三大特性之一的封装指的就是将单一事物抽象出来组合成一个类,所以我们在设计类的时候每个类中处理的是单一事物而不是某些事物的集合。 设计模式中所谓的单一职责原则,就是对一个类而言,应该仅有一个引起它变化的原…

怎么借助ChatGPT处理数据结构的问题

目录 使用ChatGPT进行数据格式化转换 代码示例 ChatGPT格式化数据提示语 代码示例 批量格式化数据提示语 代码示例 ChatGPT生成的格式化批处理代码 使用ChatGPT合并不同数据源的数据 合并数据提示语 自动合并数据提示语 ChatGPT生成的自动合并代码 结论 数据合并是…

RT-Thread内核学习

内核框架 内核是操作系统最基础也是最重要的部分,内核处于硬件层之上,内核部分包括内核库、实时内核实现。 内核库是为了保证内核能够独立运行的一套小型的类似C库的函数实现子集。这部分根据编译器不同自带C库的情况也会不同。 当使用GNU GCC编译器时&…

波奇学C++:stl的list模拟实现

list是双向带头链表。所以迭代器end()相当于哨兵卫的头。 list不支持和[]重载&#xff0c;原因在于list空间不是连续的&#xff0c;和[]的代价比较大。 访问第n个节点&#xff0c;只能用for循环&#xff0c;来实现 list<int> l; l.push_back(0); l.push_back(1); l.pu…

hive-列转行

转成 select customer_code,product_type from temp.temp_xx LATERAL VIEW explode(SPLIT(product_types,,)) table_tmp AS product_type where customer_code K100515182

C#开发WinForm之DataGridView开发

前言 DataGridView是开发Winform的一个列表展示&#xff0c;类似于表格。学会下面的基本特征用法&#xff0c;再辅以经验&#xff0c;基本功能开发没问题。 1.设置 DataGridView表格行首为序号索引, //设置 DataGridView表格行首为序号索引private void dataGridView1_RowPost…

python实现卡尔曼滤波代码详解

Kalman滤波算法的原理可以参考&#xff1a; 卡尔曼滤波理解 python中filterpy库中实现了各种滤波算法&#xff0c; 其中就包括了kalman滤波算法。 具体实现代码&#xff1a; https://github.com/rlabbe/filterpy/blob/master/filterpy/kalman/kalman_filter.py 本文针对该代码…

数据结构——布隆计算器

文章目录 1.什么是布隆过滤器&#xff1f;2.布隆过滤器的原理介绍3.布隆过滤器使用场景4.通过 Java 编程手动实现布隆过滤器5.利用Google开源的 Guava中自带的布隆过滤器6.Redis 中的布隆过滤器6.1介绍6.2使用Docker安装6.3常用命令一览6.4实际使用 1.什么是布隆过滤器&#xf…

骨传导耳机对大脑有影响吗?骨传导耳机有什么副作用

先上结论&#xff0c;骨传导耳机对大脑没有影响。骨传导耳机使用的是骨传导技术&#xff0c;声音是通过头骨骨头和颌骨给内耳传递的&#xff0c;而不是通过传统的空气传播。 简单来说&#xff0c;骨传导技术使用人类骨骼结构和声学原理来传递声音&#xff0c;这种现象我们也很常…

最新Burp Suite入门技术

Burp Suite的安装 Burp Suite是一款集成化的渗透测试工具&#xff0c;包含了很多功能&#xff0c;可以帮助我们高效地完成对Web应用程序的渗透测试和安全检测。 Burp Suite由Java语言编写&#xff0c;Java自身的跨平台性使我们能更方便地学习和使用这款软件。不像其他自动化测…

vue3 基础知识 (组件之间的通信 and vuex) 02

侬好哇 &#xff01;&#x1f60d; 文章目录 一、组件的通信 &#xff08;父传子&#xff09;二、非 Prop 的Attribute (属性&#xff09;三、组件的通信 &#xff08;子传父&#xff09;四、非父子组件的相互通信&#xff08;Provide/Inject&#xff09;五、非父子组件的相互通…