Java内存模型:JMM简单介绍

时间:2021-08-08 20:50:15

Java内存模型:JMM

java内存模型JMM(Java Memory Model)是线程间通信的控制机制,描述了程序中各变量1之间的关系,定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样底层细节:
JMM中规定了线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存2中存储了该线程以读/写共享变量的副本。线程对变量的所有操作都必须在本地内存进行,而不能直接读写主内存中的变量。
JMM的关键技术点都是围绕着多线程的原子性、可见性和有序性来建立的。

原子性(Atomicity)

原子性是指一个操作是不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。
注:对于32位系统来说,long型数据的读写不是原子性的(因为long有64位)。也就是说,如果两个线程同时对long进行写入的话(或者读取),对线程之间的结果是有干扰的。

可见性(Visibility)

可见性是指当一个线程修改了某一个共享变量的值,其他线程是否能够立即知道这个修改。
可见性问题是一个综合性问题。除了缓存优化或者硬件优化(有些内存读写可能不会立即触发,而会先进入一个硬件队列等待)会导致可见性问题外,指令重排以及编辑器的优化,都有可能导致一个线程的修改不会立即被其他线程察觉。

有序性(Ordering)

一个线程内而言代码的执行是从先往后依次执行的。但是,在并发时,程序的执行可能就会出现乱序。有序性问题的原因是程序在执行时,可能会进行指令重排,重排后的指令与原指令的顺序未必一致。不过需要强调一点,对于一个线程来说,它看到的指令执行顺序一定是一致的。也就是说指令重排是有一个基本前提的,就是保证串行语义的一致性。指令重排不会使串行的语义逻辑发生问题。
注:指令重排可以保证串行语义一致,但是没有义务保证多线程间的语义也一致。

指令重排

之所以会有指令重排这个东西,完全是因为性能考虑。因为一条指令的执行是可以分为以下几步:
• 取指 IF
• 译码和取寄存器操作数 ID
• 执行或者有效地址计算 EX
• 存储器访问 MEM
• 写回 WB
汇编指令也不是一步就可以执行完毕的,在CPU中实际工作时,它还是需要分为多个步骤依次执行的。比如,取指时会用到PC寄存器和存储器,译码时会用到指令寄存器组,执行时会使用ALU,写回时需要寄存器组。
注:ALU指算术逻辑单元。它是CPU的执行单元,主要功能是进行二进制算术运算。
由于每一个步骤都可能使用不同的硬件完成,因此,会存在一种流水线技术来执行指令,而流水线是可能中断的,中断时为了不浪费cpu资源,这时就需要指令重排。所以指令重排的存在带来了乱序的问题,但提高了CPU处理性能。

指令重排是有原则的:

Happen-Before规则

虽然Java虚拟机和执行系统会对指令进行一定的重排,但是指令重排是有原则的,并非所有的指令都可以随便改变执行位置,以下罗列了一些基本原则:
程序顺序原则:一个线程内保证语义的串行性
• volatile规则:volatile变量的写,先发生于读,这保证了volatile变量的可见性
• 锁规则:解锁(unlock)必然发生在随后的加锁(lock)前
• 传递性:A先于B,B先于C,那么A必然先于C
• 线程的start()方法先于它的每一个动作
• 线程的所有操作先于线程的终结(Thread.join())
• 线程的中断(interrupt())先于被中断线程的代码
• 对象的构造函数执行、结束先于finalize()方法
以程序顺序原则为例,比如: a=1; b=a+1;由于第2条语句依赖第一条的执行结果。因此这种情况是绝对不允许发生的。


  1. 此处的变量与Java编程时所说的变量不一样,包括了实例域、静态域和构成数组对象的元素,但是不包括局部变量与方法参数,后者是线程私有的,不会被共享.
  2. 本地内存是JMM的一个抽象概念,并不真实存在.