如果你准备过大厂Java面试,大概对这样一段面经很熟:volatile通过插入内存屏障保证了可见性和有序性。背起来很顺,但面试官只要追问一句“LoadLoad和StoreStore到底约束的是什么”,现场就容易露馅。常见的回答是LoadLoad禁止读读重排,StoreStore禁止写写重排。这话不算错,但它没有回答关键问题:为什么读读、写写在不少CPU上本来就被硬件顺序保证了,Java内存模型还在反复强调这两类屏障?在什么业务场景里,没有LoadLoad/StoreStore程序真的会出错?这篇文章就把这个问题完整摊开:从内存屏障的底层含义,到四类屏障的具体语义,再到Java里由volatile、final、锁所触发的屏障分别落在哪里,最后再说面试时怎么把这层理解讲出来。内容面向准备Java并发面试的人,也适合正在排查诡异并发问题的开发者。篇幅不短,但看完之后你再回头看那段面经,会明显感觉通透了。
1. 先从一个典型场景切入:发布者与消费者之间缺的到底是什么
假设有这样一个朴素需求:线程A负责准备一份数据,准备好之后设置一个标志位告诉线程B“可以读了”;线程B看到标志位之后,立刻去读那份数据。稍微写过并发代码的人都知道,这个“标志位”应该用volatile修饰,否则可能出现死循环或者读到旧数据。但很少有人往下想一层:volatile为什么能保证线程A写的数据一定在线程B读标志位之前可见?它的底层不就是内存屏障吗?
1.1 一个最小化的“发布-消费”模型
先看一段有问题的写法,注意这段代码只是想表达意图,本身不具备正确的内存语义,不要直接用在生产环境:
java复制public class PublishDemo {
private static int data = 0; // 普通写
private static boolean ready = false; // 普通写,问题点
public static void main(String[] args) throws Exception {
Thread writer = new Thread(() -> {
data = 42; // 1. 先写数据
ready =
