Java 内存模型 (JMM) 是 JVM 对内存访问和操作的规范,它定义了多线程编程中变量的访问规则和数据一致性问题。JMM 是一套抽象的规范,JVM 的具体实现可以根据不同的硬件平台和操作系统而有所不同,但它们都必须遵守 JMM 的基本原则。
JMM 的主要目标是确保多线程程序的正确性和可预测性。它通过定义一套规则来规范线程对共享内存的访问,从而防止数据竞争 (data race) 和内存一致性问题。JMM 的基本原则包括:
- 可见性: 线程对共享变量的修改必须及时反映到其他线程的可见范围内。
- 原子性: 对共享变量的读写操作是原子的,即不可中断。
- 有序性: 线程对共享变量的访问顺序必须与程序中的执行顺序一致。
为了实现这些基本原则,JMM 引入了以下几个关键概念:
- 主内存 (main memory): 主内存是所有线程共享的物理内存空间。
- 工作内存 (working memory): 每个线程都有自己的工作内存,它存储了该线程私有的变量副本。
- 缓存一致性协议 (cache coherence protocol): 缓存一致性协议是用来保证多个处理器缓存中的数据保持一致的协议。
当一个线程修改共享变量时,它会将修改后的值写入主内存。其他线程可以通过读取主内存中的值来获取最新的值。然而,由于缓存一致性协议的延迟,其他线程可能无法立即看到修改后的值。为了解决这个问题,JMM 引入了内存屏障 (memory barrier) 的概念。内存屏障可以强制线程立即将修改后的值写入主内存,并确保其他线程能够看到修改后的值。
Java 语言提供了 synchronized
和 volatile
两个关键字来实现线程同步和可见性。synchronized
关键字可以保证对共享变量的访问是原子的,volatile
关键字可以保证对共享变量的修改是可见的。
以下是一些演示代码,展示了如何使用 synchronized
和 volatile
关键字来实现线程同步和可见性:
class SharedCounter {
private int count = 0;
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
}
public class Main {
public static void main(String[] args) {
SharedCounter counter = new SharedCounter();
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
counter.increment();
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
counter.increment();
}
});
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Final count: " + counter.getCount());
}
}
在这个示例中,我们使用 synchronized
关键字来保证对 count
变量的访问是原子的,从而避免了数据竞争问题。
class SharedCounter {
private volatile int count = 0;
public void increment() {
count++;
}
public int getCount() {
return count;
}
}
public class Main {
public static void main(String[] args) {
SharedCounter counter = new SharedCounter();
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
counter.increment();
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
System.out.println("Current count: " + counter.getCount());
}
});
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
在这个示例中,我们使用 volatile
关键字来保证对 count
变量的修改是可见的,从而使线程 2 能够及时地看到线程 1 对 count
变量的修改。
对 Java 内存模型的深入理解对于解决并发编程中的问题至关重要。通过掌握 JMM 的基本原则和关键概念,程序员可以编写出更加健壮和可预测的多线程程序。