文章目录
定义
Java虚拟机(JVM)的内存模型(Java Memory Model,简称JMM)是用来描述Java程序在计算机内存中的运行规则,以及程序员和编译器、运行时系统之间的共享内存访问协议。JMM的目标是定义一个一致、简单且高性能的内存模型,以促进Java程序的正确性和可移植性。
JMM的主要特点:
1. 原子性(Atomicity):
- JMM保证基本数据类型的读写操作(赋值和读取,除了long和double)都是原子的。
- 对于long和double类型的非volatile变量,JMM允许虚拟机将64位数据读写操作划分为两次32位操作进行,这可能导致其他线程读取到“半个变量”的值,即非原子性。
>在Java中,原子操作是指那些不会被线程调度机制打断的操作,一旦开始,就会一直运行到结束,中间不会有任何上下文切换。然而,Java中的long和double类型的操作并不是原子操作。这是因为,尽管int等不大于32位的基本类型的操作都是原子操作,但某些JVM(特别是32位JVM)对long和double类型的操作并不是原子性的,这可能会导致错误数据的出现。这是因为long和double是64位的,而32位JVM可能无法保证对它们的操作是原子的,从而在多线程环境下可能会出现数据不一致的情况。
- 通过使用volatile关键字,可以确保long和double类型变量的读写操作也是原子的。
- 对于复合操作(如自增),JMM不能保证它们的原子性。为了实现原子性,可以使用
synchronized
关键字或者java.util.concurrent.atomic
包中的原子类。 - JMM还定义了线程间的通信规则,以及在多线程环境下如何同步共享数据。此外,JMM还提供了happens-before关系,用于描述操作之间的偏序关系,这种关系决定了操作之间的可见性和有序性。
2. 有序性(Ordering):
- 在Java程序中,为了提高性能,编译器和处理器可能会对指令进行重排序。代码的执行顺序可能会因为编译器优化、处理器指令重排等原因而发生变化。这种变化在单线程环境下通常不会影响程序的正确性,但在多线程环境下可能会导致意外的结果。
网路图片: - JMM通过禁止特定类型的重排序来保证有序性。例如,JMM通过使用内存屏障和锁机制来保证操作的有序性。例如,
volatile
关键字可以确保对变量读/写的顺序性。此外,synchronized和Lock也可以保证有序性,因为它们确保同一时刻只有一个线程能够执行同步代码块或锁保护的代码。
3. 可见性(Visibility):
(图片来源网络)
- 当一个线程修改了共享变量的值,其他线程能够立即看到这个修改,这就是可见性。但由于处理器缓存的存在,不同线程对共享变量的读写可能会导致可见性问题。
- 在Java中,如果一个变量被声明为volatile,那么对这个变量的读写就具有了可见性。也就是说,当一个线程修改了volatile变量的值,这个修改会立即被写入主内存,并且其他线程在读取该变量时会看到最新的值。
- 除了volatile之外,synchronized和final关键字也可以保证可见性。synchronized通过锁机制确保变量的修改对其他线程可见,而final关键字则确保初始化后的值对其他线程可见。
- JMM通过主内存和工作内存之间的交互来保证可见性。每个线程都有自己的工作内存,其中存储了其本地的变量副本。当需要访问共享变量时,线程首先从主内存中读取变量的值到工作内存,然后在工作内存中进行操作。操作完成后,线程会将工作内存中的值写回主内存。
volatile
关键字、synchronized
关键字和java.util.concurrent
包中的锁都可以用来保证变量的可见性。
4. 主内存与工作内存:
- JMM将内存划分为主内存和工作内存两部分。主内存是所有线程共享的存储区域,而工作内存则是每个线程私有的存储区域。
- 线程对共享变量的操作都必须在自己的工作内存中进行,然后再将结果同步回主内存。这种设计可以减少线程间的竞争和锁的开销,提高程序的执行效率。
场景比喻
想象一下,JMM就像是一个繁忙的城市交通系统,其中主内存就像是主干道,而工作内存则是每辆车的内部空间。在这个城市里,所有车辆(线程)都需要遵守一定的交通规则,以确保整个城市的顺畅运行。
1. 原子性:交通信号灯
想象交通信号灯,它确保每次只能有一辆车通过路口(对于基本数据类型,除了long和double)。而对于long和double类型的非volatile变量,信号灯有时会允许两辆车并排快速通过(非原子性),但这种情况很少见。然而,当路口设置了特殊的“volatile”标志时,信号灯就会变得更加严格,确保每次只有一辆车安全通过。
2. 可见性:广播系统
现在,想象一下城市有一个高效的广播系统。当一辆车(线程)在主干道上做了改变(修改了共享变量),这个改变会立刻通过广播传遍整个城市(写入主内存),让其他车辆(线程)都能听到最新的消息(看到最新的值)。这就是volatile关键字的作用,它就像是一个实时更新的广播系统。
3. 有序性:交通管制员
在城市里,交通管制员(JMM)会确保车辆按照规定的路线行驶,不会随意变道或插队(禁止特定类型的重排序)。特别是当车辆经过重要的路口(volatile变量)或者进入特定的车道(synchronized代码块)时,管制员会更加严格地监督,确保一切井然有序。
4. 主内存与工作内存:车辆与车库
每辆车都有自己的车库(工作内存),在这里它可以自由地做自己的事情,比如整理车内物品(处理局部变量)。但是,当它需要与其他车辆交流(访问共享变量)时,就必须把车开到主干道上(写入主内存)。同样地,当它想了解其他车辆的情况时,也会先回到自己的车库(从主内存读取数据到工作内存),然后再做出反应。
通过这样的比喻,我们可以更直观地理解JMM是如何在多线程环境下协调和管理内存的。它就像是一个精心设计的交通系统,确保所有车辆(线程)都能安全、高效地行驶在城市的大街小巷。
总之,JMM是Java并发编程的基石,它确保了Java程序在多线程环境下的正确性和一致性。了解和掌握JMM对于编写高性能、高并发的Java程序至关重要。