[Java-juc] Java 内存模型
2026年3月25日大约 6 分钟
[Java-juc] Java 内存模型
说一下你对 Java 内存模型的理解?
Java 内存模型就是定义主内存(共享内存)和工作内存(本地内存)进行交互的规则,
用来多线程环境下共享变量的内存可见性。
为什么线程要用自己的内存?
- 使用工作内存是为了提高性能,减少对主内存的频繁访问,
- 并且可以避免多个线程同时修改共享变量导致冲突。
i++是原子操作吗?
不是。
- 会先从内存中读取 i 的值。
- 然后对 i 的值进行加 1 操作。
- 最后将新的值写回内存。
说说你对原子性、可见性、有序性的理解?
总结
原子性保证操作不可中断,可见性保证变量修改后线程能看到最新值,有序性保证代码执行顺序一致,可以通过 volatile、synchronized 和 CAS 机制来保证这些特性。
- 原子性:操作不可分割,要么全部执行成功,要么完全不执行。(需要使用锁或原子类保证原子性)
- 可见性:一个线程对共享变量修改,能够及时被其他线程看见。(需要使用
volatile保证可见性) - 有序性:指程序是否按照代码的顺序执行。
说说什么是指令重排?
指令重排是编译器或处理器为了提高程序的性能,对指令执行顺序进行重新排列的优化技术。
- 指令重排可能导致多线程环境下的可见性问题和有序性问题。
- 有三种重排序:
- 编译器重排序:在不改变单线程运行结果的前提下,编译器可以优化语句的执行顺序。
- 并行指令重排序:处理器将多条指令并行执行时,在不存在数据依赖的情况下,处理器可以改变程序的执行顺序。
- 内存系统重排序:处理器使用缓存和读写缓冲区时,使得数据加载、存储操作看起来是乱序执行的。
数据依赖
数据依赖是指,如果两个操作同时访问同一个共享变量,如果这两个操作有一个写操作,那么这两个操作之间就存在数据依赖。
- 具有依赖性的操作是不会被重排的。
as-if-serial 了解吗?
不管如何重排序,单线程程序执行结果不会改变。
happens-before 了解吗?
- 字面含义:A 发生在 B 之前。
Happens-Before 是 Java内存模型定义的规则,用来保证两个操作之间的可见性。
happens-before 六大规则
- 程序顺序规则:单线程内部,代码按照顺序执行。
- 监视器锁规则:前一个线程释放锁操作发生在后一个线程获取锁之前。
- volatile 变量规则:对一个 volatile 修饰的字段写操作,发生在后续对这个字段的读操作之前。
- 传递性规则:如果A发生在B之前,B发生在C之前,那么A发生在C之前。
- 线程启动规则:如果线程A执行线程B的
start(),那么线程A的B.start()happens-before 线程B的任何操作。 - 线程终止规则:如果线程A执行线程B的
join(),那么join()之后的代码,必须等线程B执行完毕后才能执行。
内存屏障是什么,有什么用?
什么内存屏障?
内存屏障是一种屏障指令,一种用于控制 CPU 和编译器对内存操作重排序的机制。
内存屏障的功能:
- 阻止屏障两边的指令重排序。
- 写数据时加屏障,强制把缓冲区的数据刷回主内存中。
- 读数据时加屏障,强制让工作内存的缓存数据失效,重新从主内存中获取新的数据。
基本分类
- 读屏障 Load Barrier:在读指令之前插入读屏障,强制让工作内存中缓存失效,重新从主内存中获取最新数据。
- 写屏障 Store Barrier:在写指令之后插入写屏障,强制将工作内存的数据刷回主内存中。
重排序和内存屏障的关系
重排序可能给程序带来问题,因此我们需要告诉JVM这里不需要排序。
JVM 本身为了保证可见性:
- 对于编译器的重排序,JMM会根据重排序的规则,禁止特定类型编译器的重排序。
- 对于处理器的重排序,Java编译器会在适当位置插入内存屏障指令,来禁止特定类型的处理器重排序。
JMM的内存屏障
- LoadLoad Barrier:保证前面的读操作完成之后,后面的读操作才能执行。
- LoadStore Barrier:保证前面的读操作完成之后,后面的写操作才能执行。
- StoreStore Barrier:保证前面的写操作完成之后,后面的写操作才能执行。
- StoreLoad Barrier:保证前面的写操作完成之后,后面的读操作才能执行。
volatile 了解吗?重点
volatile用于修饰变量,实际上就是一种轻量级的同步机制。
volatile修饰的变量,具有以下特点:
- 保证其可见性
- 禁止指令重排
- 不保证其原子性
volatile 怎么保证可见性的?
- 对 volatile 修饰的变量进行读操作时,会在这个读操作后面插入LoadLoad Barrier和LoadStore Barrier,强制让工作内存的缓存失效,重新从主内存中获取最新数据。
- 对 volatile 修饰的变量进行写操作时,会在这个写入操作前面插入StoreStore Barrier;后面插入StoreLoad Barrier,强制将工作内存的数据刷回主内存中。
volatile 怎么保证有序性的?
JVM 会在 volatile 变量的读写操作前后插入内存屏障,禁止特定类型的重排序,来保证有序性。
单线程下不加volatile和加volatile性能开销有很大区别吗?
单线程下,volatile 的性能开销相对较小,因为没有线程竞争和上下文切换的开销。主要的影响来自于内存屏障。
volatile 和 synchronized 的区别?
- volatile 只能修饰变量,synchronized 可以修饰方法或代码块。
- volatile 只能保证可见性和有序性,不保证原子性;synchronized 可以保证可见性、有序性和原子性。
volatile 加在基本类型和对象上的区别?
- volatile 修饰基本类型变量时,保证该变量的可见性和有序性;
- 修饰对象时,保证该对象引用的可见性和有序性,对象内部是无法保证的。