前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java 内存模型中的 happen-before 是什么?

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

作者头像
王小明_HIT
发布2020-05-14 20:21:19
1.1K0
发布2020-05-14 20:21:19
举报
文章被收录于专栏:程序员奇点程序员奇点

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 的关系

image

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

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

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

JMM 可以解决什么问题?

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

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

  • https://en.wikipedia.org/wiki/Memory_ordering

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

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

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

JVM 开发者

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

Java 应用工程师

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

image

Java 内存模型的抽象定义

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

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

image

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

  • 内存屏障
  • 禁止重排序

以 volatile 变量为例:

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

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

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

  • http://gee.cs.oswego.edu/dl/jmm/cookbook.html
代码语言:javascript
复制
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关系的图形化表现形式如下:

image

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

image

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

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

image

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

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

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

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

image

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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-05-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员奇点 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Java 内存模型中的 happen-before 是什么?
    • happen-before 原则
      • happen-before 与 JMM 的关系
      • 如何学习 Java 内存模型(JMM)
    • JMM 可以解决什么问题?
      • 简化多线程编程,保证程序可移植性
      • 对指令重排序提供清晰的规范
    • Java 内存模型的抽象定义
      • JMM 是怎么解决可见性的问题
        • JMM 内部是怎样实现 happen-before 原则的?
        • 以 volatile 变量为例:
        • volatile 使用了内存屏障实现可见性
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档