JMM-内存模型
承灿 2023/7/6
Java 内存模型(JMM)是一个抽象的概念,并不真实存在,它描述了一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。
JMM 关于同步的规定:
- 线程解锁前,必须把共享变量的值刷新回主内存。
- 线程加锁时,将清空工作内存中的共享变量,从而使用共享变量时需要从主内存重新读取最新的值(注意,加锁与解锁是同一把锁)。
- 线程解锁前,对于该锁保护的每个更新过的共享变量,必须立即同步到主内存中。
- 线程加锁时,对于该锁保护的每个共享变量,必须立即从主内存中读取最新的值,或者清空工作内存中的值,在使用前从主内存中重新获取。
# JMM 的主要特性:
- 原子性: 原子性是指一个操作是不可中断的,即使是在多个线程同时执行的时候,一个操作一旦开始,就不会被其他线程干扰。
- 可见性: 可见性是指当一个线程修改了某个共享变量的值,其他线程能够立即得知这个修改。JMM 通过
volatile
关键字来保证变量修改的可见性。当一个共享变量被volatile
修饰时,它会保证修改的值立即同步到主内存,当有其他线程需要读取时,它会直接读取主内存中的最新值。 - 有序性: 有序性是指程序执行的顺序按照代码的先后顺序执行。在 Java 内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
# happens-before
原则:
这是 JMM 的重要概念,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须存在 happens-before 关系。几个重要的 happens-before 原则如下:
- 程序次序规则:一个线程中的每个操作,happens-before 于该线程中的任意后续操作。
- 监视器锁规则:对一个锁的解锁,happens-before 于随后对这个锁的加锁。
volatile
变量规则:对一个volatile
域的写,happens-before 于任意后续对这个volatile
域的读。- 传递性:如果 A happens-before B,且 B happens-before C,那么 A happens-before C。
# 示例:
考虑以下代码示例:
javaCopy codepublic class SharedObject {
private int count = 0;
public void incrementCount() {
count++;
}
public int getCount() {
return count;
}
}
public class ThreadExample extends Thread {
private SharedObject sharedObject;
public ThreadExample(SharedObject sharedObject) {
this.sharedObject = sharedObject;
}
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
sharedObject.incrementCount();
}
}
public static void main(String[] args) throws InterruptedException {
SharedObject sharedObject = new SharedObject();
ThreadExample thread1 = new ThreadExample(sharedObject);
ThreadExample thread2 = new ThreadExample(sharedObject);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("Final count is: " + sharedObject.getCount());
}
}
在这个示例中,如果你运行这个程序,最终输出的 count
值可能会小于 2000。这是因为 count++
操作不是原子性的,它包括读取变量 count
的当前值、增加 1 和写回新值三个步骤。由于没有同步措施,两个线程可能在同一时间执行 count++
操作,导致一些增量丢失。
为了修复这个问题,你可以使用 synchronized
关键字或 AtomicInteger
类来确保原子性、可见性和有序性。这些工具和关键字是 Java 平台提供的并发工具,是 JMM 规则在语言层面的实现。