Java内存模型(Java Memory Model, JMM)是Java虚拟机(JVM)规范中定义的一种抽象模型,用于描述多线程环境下,线程如何与主内存和工作内存交互,以及如何保证线程之间的可见性、有序性和原子性。JMM是理解Java并发编程的基础,尤其是在多线程环境下,如何正确同步共享数据。
1. Java内存模型的核心概念
(1)主内存(Main Memory)
-
主内存是所有线程共享的内存区域,存储了所有的变量(实例字段、静态字段等)。
-
主内存是JVM堆内存的一部分。
(2)工作内存(Working Memory)
-
每个线程都有自己的工作内存,存储了该线程使用的变量的副本。
-
工作内存是线程私有的,可能是CPU缓存或寄存器。
(3)内存交互操作
JMM定义了以下8种操作来实现主内存和工作内存之间的交互:
-
lock(锁定):作用于主内存变量,标识变量为线程独占状态。
-
unlock(解锁):作用于主内存变量,释放锁定状态。
-
read(读取):从主内存读取变量到工作内存。
-
load(加载):将read操作读取的值放入工作内存的变量副本中。
-
use(使用):将工作内存中的变量值传递给执行引擎(如CPU)。
-
assign(赋值):将执行引擎计算的结果赋值给工作内存中的变量。
-
store(存储):将工作内存中的变量值传送到主内存。
-
write(写入):将store操作传送的值写入主内存的变量中。
2. Java内存模型的三大特性
(1)原子性(Atomicity)
-
原子性是指一个操作是不可分割的,要么全部执行成功,要么全部不执行。
-
JMM保证对基本数据类型的读写操作是原子的(如
int
、boolean
等)。 -
对于
long
和double
类型,在32位JVM上可能不是原子的,需要使用volatile
关键字保证原子性。
(2)可见性(Visibility)
-
可见性是指一个线程对共享变量的修改,能够及时被其他线程看到。
-
JMM通过以下机制保证可见性:
-
volatile
关键字:确保变量的修改对所有线程立即可见。 -
synchronized
关键字:在释放锁之前,会将工作内存中的变量刷新到主内存。 -
final
关键字:确保变量在构造完成后对其他线程可见。
-
(3)有序性(Ordering)
-
有序性是指程序执行的顺序按照代码的先后顺序执行。
-
JMM通过以下机制保证有序性:
-
volatile
关键字:禁止指令重排序。 -
synchronized
关键字:确保同一时刻只有一个线程执行同步代码块。 -
happens-before
原则:定义了一系列规则,确保某些操作的有序性。
-
3. Happens-Before原则
Happens-Before是JMM中定义的一组规则,用于描述多线程环境下操作之间的可见性和有序性。以下是Happens-Before的主要规则:
-
程序顺序规则:在一个线程内,代码的执行顺序按照程序的书写顺序执行。
-
锁规则:一个
unlock
操作先行发生于后续对同一个锁的lock
操作。 -
volatile
规则:对一个volatile
变量的写操作先行发生于后续对这个变量的读操作。 -
线程启动规则:
Thread.start()
先行发生于线程内的任何操作。 -
线程终止规则:线程中的所有操作先行发生于线程的终止检测(如
Thread.join()
)。 -
传递性规则:如果A happens-before B,且B happens-before C,那么A happens-before C。
4. Volatile关键字
volatile
是JMM中用于保证可见性和有序性的关键字。它的作用包括:
-
保证可见性:对
volatile
变量的修改会立即刷新到主内存,其他线程读取时会从主内存中获取最新值。 -
禁止指令重排序:JVM会禁止对
volatile
变量的读写操作进行重排序。
示例:
public class VolatileExample { private volatile boolean flag = false; public void writer() { flag = true; // 写操作 } public void reader() { if (flag) { // 读操作 System.out.println("Flag is true"); } } }
5. Synchronized关键字
synchronized
是JMM中用于保证原子性、可见性和有序性的关键字。它的作用包括:
-
保证原子性:同一时刻只有一个线程可以执行同步代码块。
-
保证可见性:在释放锁之前,会将工作内存中的变量刷新到主内存。
-
保证有序性:同步代码块内的操作不会被重排序。
示例:
public class SynchronizedExample { private int count = 0; public synchronized void increment() { count++; // 原子操作 } public int getCount() { return count; } }
6. Final关键字
final
关键字用于修饰变量、方法和类。在JMM中,final
关键字可以保证可见性:
-
final
变量:在构造完成后,对其他线程可见。 -
final
方法:不能被子类重写。 -
final
类:不能被继承。
示例:
public class FinalExample { private final int value; public FinalExample(int value) { this.value = value; // 构造完成后,value对其他线程可见 } public int getValue() { return value; } }
7. 内存屏障(Memory Barrier)
内存屏障是JMM中用于控制指令重排序和内存可见性的底层机制。常见的屏障包括:
-
LoadLoad屏障:确保前面的Load操作先于后面的Load操作。
-
StoreStore屏障:确保前面的Store操作先于后面的Store操作。
-
LoadStore屏障:确保前面的Load操作先于后面的Store操作。
-
StoreLoad屏障:确保前面的Store操作先于后面的Load操作。
8. 总结
Java内存模型(JMM)是理解Java并发编程的基础,它定义了多线程环境下线程如何与内存交互,以及如何保证原子性、可见性和有序性。