Load-Load屏障的核心作用是确保Load1完成后再执行Load2,防止读-读重排序;它仅约束读操作顺序,不保证写可见性、不刷新缓存,也不替代同步机制。

Load-Load 屏障的核心作用,是强制约束两个读操作(Load1 和 Load2)的执行顺序,确保 Load1 完成并把数据真正加载进寄存器或本地缓存后,Load2 才能开始执行。它不解决写可见性,也不刷新缓存到主内存,只管“读—读”之间的先后逻辑。
为什么需要防止 Load-Load 重排序?
现代 CPU 为提升吞吐量,允许在无数据依赖时提前执行后续读操作。比如:
- CPU 可能发现 Load2 所需地址已缓存命中,而 Load1 正在等待 L3 或主存响应,于是先执行 Load2;
- 编译器或 JIT 在生成字节码或机器码时,也可能把靠后的读指令提到前面,只要语义上不冲突;
- 这种优化在单线程下完全安全,但在多线程中可能破坏程序逻辑依赖——例如 Load2 的地址计算依赖 Load1 读出的指针值。
Load-Load 屏障如何起作用?
它在硬件和 JVM 两层协同生效:
Miller (mlr) 是一个命令行工具,用于查询、整形和重新格式化名称索引数据,如 CSV、TSV、JSON 和 JSON Lines。它将 awk、sed、cut、join 和 sort 的功能整合到一个专为结构化数据处理而构建的单一工具中。
- 在 x86/x64 平台上,JVM 通常不插入显式指令(因为 x86 的内存模型天然禁止 Load-Load 乱序),但会在 ARM、RISC-V 等弱一致性架构上生成 lfence 或等效指令;
- JIT 编译器识别到 volatile 读、Unsafe.loadFence() 或某些同步模式(如双重检查锁中的第一次 null 判断)时,会主动插入该屏障;
- 屏障生效后,CPU 必须等 Load1 的数据进入架构寄存器(architectural register),且所有相关依赖(如地址计算)完成,才允许发起 Load2 的地址解码与访存请求。
典型应用场景
不是所有连续读都需要 Load-Load 屏障,只有当存在隐式依赖时才关键:
- 对象字段的链式读取:比如 obj.flag 为 true 后再读 obj.value,若 flag 是 volatile,其读操作自带 Load-Load 语义,保证 value 读取能看到最新值;
- 基于状态的分支读取:先读状态位(如 initialized),再根据结果读配置数据,避免读到未初始化的旧缓存副本;
- 无锁数据结构中的哨兵检查:如读 head 节点后,再读其 next 字段,必须确保 head 已稳定加载,否则 next 可能是野指针。
它不能做什么?
明确区分能力边界,避免误用:
- 不保证 Load1 的结果对其他 CPU 立即可见(那是 StoreStore 或 StoreLoad 的事);
- 不阻止 Load1 和 Store 操作之间的重排(需要 LoadStore 屏障);
- 不替代 volatile 写或 synchronized —— 它只约束读操作顺序,不提供原子性或互斥。










