JAVA中 什么是JMM?
在 Java 并发编程中,Java 内存模型(JMM)一直是一个必须要深入理解的重要概念。要理解 JMM,我们首先需要理解 CPU 缓存模型和指令重排序。
从 CPU 缓存模型说起
为什么需要 CPU 高速缓存?
为了理解 CPU 高速缓存的重要性,我们可以类比我们开发网站后台系统使用的缓存(比如 Redis)。缓存的主要目的是为了提升数据访问速度,缓解处理速度和数据存取速度之间的矛盾。同样的道理,CPU 高速缓存(CPU Cache)是为了缓解 CPU 处理速度和内存访问速度之间的不匹配。
可以简单理解为:
- 内存缓存硬盘的数据:内存的处理速度比硬盘快很多,这样可以提高数据存取速度。
- CPU 缓存内存的数据:CPU 的处理速度远远高于内存的速度,因此需要缓存一部分内存数据以加速访问。
CPU Cache 的工作方式
现代的 CPU Cache 通常分为三级:L1、L2 和 L3 Cache。有些 CPU 可能还有 L4 Cache,但并不常见。CPU Cache 的工作方式可以简单描述为:
- 读取数据:将一部分内存数据复制到 CPU Cache 中。
- 处理数据:当 CPU 需要数据时,可以直接从 CPU Cache 中读取。
- 写回数据:运算完成后,将结果写回主内存。
内存缓存不一致性问题
由于 CPU 和主内存之间存在缓存,当多个线程同时操作共享数据时,可能会导致内存数据不一致的问题。例如,在执行 i++ 操作时,如果两个线程同时从 CPU Cache 中读取 i=1,然后各自执行 i++ 并写回内存,最终结果可能是 i=2,而不是预期的 i=3。
缓存一致性协议
为了防止上述问题,CPU 制定了一系列缓存一致性协议(如 MESI 协议)来确保数据一致性。MESI 协议将每个缓存行分为四种状态:修改(Modified)、独占(Exclusive)、共享(Shared)和无效(Invalid)。这些状态帮助协调不同 CPU 缓存之间的数据一致性。
Java 内存模型(JMM)
Java 内存模型(JMM)定义了 Java 程序在多线程环境下如何与内存交互,确保了在不同平台和不同硬件上程序的行为是一致的。
JMM 的基本原则
- 原子性:确保基本操作的原子性。
- 可见性:确保一个线程对变量的修改可以被其他线程看见。
- 有序性:确保指令按照一定的顺序执行。
JMM 如何解决内存缓存不一致性问题
在 JMM 中,通过以下几种方式来解决内存缓存不一致性问题:
- volatile 关键字:保证变量的可见性和有序性。
- synchronized 关键字:保证代码块的原子性和内存可见性。
- final 关键字:确保对象的初始化安全性。
源码解析
volatile 关键字
java
public class VolatileExample {
private volatile boolean flag = false;
public void writer() {
flag = true;
}
public void reader() {
if (flag) {
// Do something
}
}
}
在上面的代码中,volatile 关键字确保 flag 的修改对所有线程是可见的,并且禁止指令重排序。
synchronized 关键字
java
public class SynchronizedExample {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
在上面的代码中,synchronized 关键字确保 increment 和 getCount 方法的原子性和可见性。
final 关键字
java
public class FinalExample {
private final int x;
public FinalExample(int x) {
this.x = x;
}
public int getX() {
return x;
}
}
在上面的代码中,final 关键字确保 x 的初始化安全性,所有线程都能看到 x 的正确值。
实际应用场景
在实际开发中,理解并正确使用 JMM 对于编写高效、安全的并发程序至关重要。例如,在设计高并发的 Web 服务时,需要确保共享变量的修改对所有线程是可见的,并且避免指令重排序带来的潜在问题。
示例:高效的并发计数器
java
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicCounter {
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet();
}
public int getCount() {
return count.get();
}
public static void main(String[] args) {
AtomicCounter counter = new AtomicCounter();
Thread t1 = new Thread(counter::increment);
Thread t2 = new Thread(counter::increment);
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Final count: " + counter.getCount());
}
}
在上面的代码中,AtomicInteger 保证了 increment 操作的原子性和可见性,是一种高效的并发计数器实现方式。