Java 内存模型中的 happen-before 是什么?

简介: Java 内存模型中的 happen-before 是什么?

Java  内存模型中的 happen-before 是什么?



Happen-before 关系,是Java 内存模型中保证多线程可见性的机制,也是早期语言规范中含糊可见性概念的一个精确定义。

它的具体表现形式,包括但远不止 synchronized,volatile,lock 操作顺序等方面。、


happen-before 原则


  • 程序顺序规则:一个线程内执行的每个操作,都保证 happen-before 后面的操作,这样就保证了程序顺序规则,
  • volatile 变量规则:对于 volatile 变量,对他的写操作,保证 happen-before 在随后对改变量的读取操作。
  • 监视器锁规则:对于一个锁的解锁操作,保证 happen-before 加锁操作。
  • 线程启动 happen-before 规则:Thread 对象的 start() 方法先行于此线程的每一个动作
  • 线程中断 happen-before 规则:对线程 interrupt() 方法的调用先行发生于被中断线程的代码检测到中断事件的发生。
  • 线程终结规则:假定线程A在执行的过程中,通过制定ThreadB.join()等待线程B终止,那么线程B在终止之前对共享变量的修改在线程A等待返回后可见。
  • 对象终结规则,一个对象的初始化完成happen-before 于 finalizer() 方法的开始
  • 传递性:happen-before 存在传递性,a happen-before b ,b happen-before c ,那么  a happen-before c 。

happen-before 保障了顺序执行,也包括了内存读写的操作顺序。


happen-before 与 JMM 的关系


640.png


如何学习 Java 内存模型(JMM)


JMM 可以看作是深入理解Java并发编程、编译器和JM内部机制的必要条件,但这同时也是个容易让初学者无所适从的主题。

  • 明确目的,建议不要一头扎进各种CPU体系结构,纠结于不同的缓存、流水线、执行单元等。这些东西虽然很酷,但其复杂性是超乎想象的,很可能会无谓增加学习难度,也未必有实践价值。
  • 克制住对”秘籍"的诱惑。有些时候,某些编程方式看起來能起到特定效果,但分不清是实现差异导致的表现,还是"规范″要求的行为,就不要依赖于这种"表现"去编程,尽量遵循语言规范进行,这样我们的应用行为才能更加可靠、可预计。


JMM 可以解决什么问题?


简化多线程编程,保证程序可移植性


Java  是最早尝试提供内存模型的语言,可简化多线程编程,保障程序可移植。早期的 C/C++ 不存在内存模型的概念,依赖处理器本身的内存一致性模型。但是不同的处理器差异比较大,不能保证 C++ 程序在处理器A 可以运行,在处理器B 上也可以运行。

对指令重排序提供清晰的规范


过于范范的内存模型定义,有很多模棱两可之处,对 synchronized 或者 volatile 产生的指令重排序问题,如果没有清晰的规范,不能保证一些多线程程序的正确性。

所以,Java迫切需要一个完善的JMM,能够让普通Java开发者和编译器、JVM工程师,能够淸地达成共识。换句话说,可以相对简单并准确地判断岀,多线程程序什么样的执行序列是符合规范的。


JVM 开发者


对于编译器、JVM开发者,关注点可能是如何使用类似内存屏( Memory-Barrier)之类技术,保证执行结果符合JMM的推断。


Java 应用工程师


对于Java应用开发者,则可能更加关注 volatile、 synchronized等语义,如何利用类{ happen- before的规则,写出可靠的多线程应用。

640.png

Java 内存模型的抽象定义


包含本地内存和主内存的定义

640.png


JMM 是怎么解决可见性的问题

640.png

JMM 内部是怎样实现 happen-before 原则的?


  • 内存屏障
  • 禁止重排序


以 volatile 变量为例:


  • 对改变量的写操作之后,编译器插入一个写屏障
  • 对改变量的读操作之前,编译器会插入一个读屏障

内存屏障能够在类似变量读、写操作之后,保证其他线程对 volatile变量的修改对当前线程可见,或者本地修改对其他线程提倛可见性。换句话说,线程写入,写屏障会通过类似强迫刷出处理器缓存的方式,让其他线程能够拿到最新数值


如果你对更多内存屏障的细节感兴趣,或者想了解不同体系结构的处理器模型,建议参考JSR-133相关文档,我个人认为这些都是和特定硬件相关的,内存屏障之类只是实现JMM规范的技术手段,并不是规范的要求。


class VolatileExample {
int  a = 0;
volatile boolean flag= false;
public void writer(){
   a=1;        // 1
   flag = true; //2
}
public void reader(){
  if(flag){  //3
    int i = a ;//4
    ...
  }
}

假设线程A执行 writer方法之后,线程B执行 reader0方法。根据 happens-before规则,这个过程建立的 happens-before关系可以分为3类:


  1. 根据程序次序规则,1 happens-before2;3 happens-before4。
  2. 根据 volatile 规则,2 happens-before3
  3. 根据 happens-before的传递性规则,1 happens-before4。


上述 happens-before关系的图形化表现形式如下:


640.png


在上图中,每一个箭头链接的两个节点,代表了一个 happens-before关系。黑色箭头表示程序顺序规则;橙色箭头表示 volatile规则;蓝色箭头表示组合这些规则后提供的 happens-before保证。最终读取到的i 就是 1 。

640.png



线程A在写flag变量后,本地内存A中被线程A更新过的两个共享变量的值被刷新到主内存中。此时,本地内存A和主内存中的共享变量的值是一致的。


当读一个 volatile变量时,JMM会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。如图所示,在读flag变量后,本地内存B包含的值已经被置为无效。此时,线程B必须从主内存中读取共享变量。线程B的读取操作将导致本地内存B与主内存中的共享变量的值变成一致。


640.png


  • 线程A写一个 volatile变量,实质上是线程A向接下来将要读这个 volatile变量的某个线程发出了(其对共享变量所做修改的)消息
  • 线程B读一个 volatile变量,实质上是线程B接收了之前某个线程发出的(在写这个volatile变量之前对共享变量所做修改的)消息
  • 线程A写一个 volatile变量,随后线程B读这个 volatile变量,这个过程实质上是线程A通过主内存向线程B发送消息。


有序性,原子性,可见性是线程安全的基本保障。


volatile 使用了内存屏障实现可见性


  • 在每个 volatile写操作的前面插入一个Storestore屏障。
  • 在每个 volatile写操作的后面插入一个Storeload屏障。


640.png



  • 在每个 volatile读操作的后面插入一个Loadload屏障。
  • 在每个 volatile读操作的后面插入一个Loadstore屏障。


640.png



我们经常会说 volatile b比synchronized之类更加轻量,但轻量也仅仅是相对的, volatile的读、写仍然要比普通的读写要开销更大,所以如果你是在性能高度敏感的场景,除非你确定需要它的语义,不然慎用。


相关文章
|
23天前
|
算法 安全 Java
Java内存管理:深入理解垃圾收集器
在Java的世界里,内存管理是一块基石,它支撑着应用程序的稳定运行。本文将带你走进Java的垃圾收集器(GC),探索它是如何默默守护着我们的内存安全。我们将从垃圾收集的基本概念出发,逐步深入到不同垃圾收集器的工作机制,并通过实例分析它们在实际应用中的表现。文章不仅旨在提升你对Java内存管理的认识,更希望你能通过这些知识优化你的代码,让程序运行更加高效。
36 3
|
29天前
|
Kubernetes Cloud Native Java
云原生之旅:从容器到微服务的演进之路Java 内存管理:垃圾收集器与性能调优
【8月更文挑战第30天】在数字化时代的浪潮中,企业如何乘风破浪?云原生技术提供了一个强有力的桨。本文将带你从容器技术的基石出发,探索微服务架构的奥秘,最终实现在云端自由翱翔的梦想。我们将一起见证代码如何转化为业务的翅膀,让你的应用在云海中高飞。
|
9天前
|
监控 算法 Java
Java中的内存管理:理解Garbage Collection机制
本文将深入探讨Java编程语言中的内存管理,特别是垃圾回收(Garbage Collection, GC)机制。我们将从基础概念开始,逐步解析垃圾回收的工作原理、不同类型的垃圾回收器以及它们在实际项目中的应用。通过实际案例,读者将能更好地理解Java应用的性能调优技巧及最佳实践。
37 0
|
4天前
|
存储 缓存 Java
java线程内存模型底层实现原理
java线程内存模型底层实现原理
java线程内存模型底层实现原理
|
6天前
|
Java 编译器
深入理解Java内存模型:从基础到高级
本文旨在通过通俗易懂的方式,引导读者深入理解Java内存模型(JMM)的核心概念和工作原理。我们将从简单的基础知识入手,逐步探讨重排序、顺序一致性问题以及volatile关键字的实现机制等高级主题。希望通过这篇文章,你能够对Java内存模型有一个清晰、全面的认识,并在实际编程中有效地避免并发问题。
|
3天前
|
存储 算法 Java
深入理解Java内存管理
本文将通过通俗易懂的语言,详细解析Java的内存管理机制。从JVM的内存结构入手,探讨堆、栈、方法区等区域的具体作用和原理。进一步分析垃圾回收机制及其调优方法,最后讨论内存泄漏的常见场景及防范措施。希望通过这篇文章,帮助读者更好地理解和优化Java应用的内存使用。
|
8天前
|
监控 算法 Java
Java中的内存管理与垃圾回收机制
本文将深入探讨Java编程语言中的内存管理方式,特别是垃圾回收(Garbage Collection, GC)机制。我们将了解Java虚拟机(JVM)如何自动管理内存,包括对象创建、内存分配以及不使用对象的回收过程。同时,我们还将讨论不同的垃圾回收算法及其在不同场景下的应用。
|
7天前
|
监控 算法 Java
深入理解Java中的垃圾回收机制在Java编程中,垃圾回收(Garbage Collection, GC)是一个核心概念,它自动管理内存,帮助开发者避免内存泄漏和溢出问题。本文将探讨Java中的垃圾回收机制,包括其基本原理、不同类型的垃圾收集器以及如何调优垃圾回收性能。通过深入浅出的方式,让读者对Java的垃圾回收有一个全面的认识。
本文详细介绍了Java中的垃圾回收机制,从基本原理到不同类型垃圾收集器的工作原理,再到实际调优策略。通过通俗易懂的语言和条理清晰的解释,帮助读者更好地理解和应用Java的垃圾回收技术,从而编写出更高效、稳定的Java应用程序。
|
13天前
|
监控 算法 Java
Java中的内存管理:理解垃圾回收机制的深度剖析
在Java编程语言中,内存管理是一个核心概念。本文将深入探讨Java的垃圾回收(GC)机制,解析其工作原理、重要性以及优化方法。通过本文,您不仅会了解到基础的GC知识,还将掌握如何在实际开发中高效利用这一机制。
|
14天前
|
存储 监控 算法
Java中的内存管理与垃圾回收机制解析
本文深入探讨了Java编程语言中的内存管理策略和垃圾回收机制。首先介绍了Java内存模型的基本概念,包括堆、栈以及方法区的划分和各自的功能。进一步详细阐述了垃圾回收的基本原理、常见算法(如标记-清除、复制、标记-整理等),以及如何通过JVM参数调优垃圾回收器的性能。此外,还讨论了Java 9引入的接口变化对垃圾回收的影响,以及如何通过Shenandoah等现代垃圾回收器提升应用性能。最后,提供了一些编写高效Java代码的实践建议,帮助开发者更好地理解和管理Java应用的内存使用。

热门文章

最新文章