简述
happen-before 简化的字面意思就是“某事件在另一事件之前发生”。
happen-before 关系是 Java 内存模型中保证多线程操作可见性的机制。
它可以保证语句的执行顺序,及对内存读写的操作顺序。
为了提高效率,计算机中存在编译器指令重排和CPU乱序执行等优化方式;
这类优化方式在多线程场景中可能会导致多线程存取共享数据时数据状态不一致的问题。
线程内执行的每个操作都被保证在各自后续操作之前被执行——也就是程序会照着我们在编码时写定的先后顺序执行。
synchronized、volatile、lock 等相关代码的操作顺序都属于该机制的一部分。
为什么需要JMM(Java Memory Model)
CPU在执行程序时会将数据从内存读取到各自内核缓存中,且对该数据的修改可能只体现在该缓存中。
如果按照程序逻辑该数据是多线程共享的,那可能会导致其它CPU在使用该数据时得到的是旧值,而非之前那个CPU修改后的值。
早期的Java内存模型定义不够严谨,无法保证Java程序在各种硬件平台上行为的一致性。这导致Java程序在部分平台上运行时的效果与预期不符。
为了达到“书写一次,到处执行”的目标,Java自身需要有一套完善的内存模型,以屏蔽上述因不同硬件平台内存模型差异导致的程序行为不一致。
有了这么一套完善的内存模型,普通Java开发者、编译器及JVM开发者之间才能达成清晰的共识,相对简单地准确判断出什么样的多线程执行序列是符合规范的。
JVM开发者和编译器更关注于如何使用类似内存屏障(Memory-Barrier)之类的技术,来保证执行结果符合JMM的推断。
普通Java开发者更关注于volatile、synchronized等关键字的语义,并利用类似 happen-before 的规则,写出可靠的多线程程序。
JMM是如何解决可见性等各种问题的
JMM 通常是利用内存屏障,通过禁止某些指令重排(包括编译器重排、处理器重排),来保证内存可见性。这也就是 happen-before 规则。
如,对于 volatile 变量,内存屏障机制会:
在“对该变量的写操作”之后,(编译器)加入一个“写屏障”;
在“对该变量的读操作”之前,(编译器)加入一个“读屏障”。
程序可以利用这些屏障,确保当前线程对共享变量的修改是对其它线程可见的。
可以近似理解为,在内存屏障下,当线程更改某共享数据后,屏障机制会强制将数据刷出处理器缓存,让其它线程拿到最新的值。
volatile变量的读写开销,比普通变量读取更高,比 synchronized 更低。
注:虽然 volatile 在某些场景中可以实现更低成本的多线程同步,但它并不能完全取代 synchronized。