【并发编程】最通俗的话讲解 happens-before 原则

推荐阅读


happens-before是JMM最核心的概念。对应Java程序员来说,理解happens-before是理解 JMM的关键。本章中笔者将用最白话的文字来阐述到底什么是 happens-before 原则以及其作用。

1、矛盾点


对于开发人员而言,我们希望内存模型易于理解易于编程,因此期待一个强内存模型来实现代码。但对于编译器和处理器而言,其希望内存模型对其束缚越少越好,这样其就可以利用一些手段优化代码执行,提升效率,编译器和处理器希望提供的是一个弱内存的模型。


所以,基于这矛盾点,JSR-133 的专家们就需要在这两者中间找到一个合理的平衡点:

  1. 要为开发人员找到简单易用,且提供了足够强的内存可见性

  2. 同时也要对编译器和处理尽可能的宽松,尽量利用编译器和处理的优化能力


观察下面的代码示例

double PI = 3.1415; // 操作 A
double r = 3; // 操作 B
double area  = PI * r * r; // 操作 C


一个强内存模型所期望的原则是:

  1. A happens-before B
  2. B happens-before C
  3. A happens-before C


在这三个规则中,我们知道规则2和3 是必须的,但是规则1则没有必要。因此JMM把 happend-before 禁止重排序的规则分为了会改变结果的重排序以及不会改变结果的重排序。JMM 对于前者则禁止编译器和处理器重排序,对于后者JMM不做要求,允许编译器或者处理器进行重排序。


image.png
图片引用 《Java 并发编程的艺术 》P126页 图3-33 JMM 设计示意图


JMM 向程序员提供的 happens-before 规则能满足程序员的需求。JMM 的 happens-before 规则不但简单易懂,而且也向程序员提供了足够强的内存可见性保证。同时 JMM 对编译器和处理器的束缚已经尽可能少。从上面的分析可以看出,JMM其实是在遵 循一个基本原则: ** ****只要不改变程序的执行结果(指的是单线程程序和正确同步的多线程程序), 编译器和处理器怎么优化都行。**

2、happens-before 定义


JSR-133使用 happens-before 的概念来指定两个操作之间的执行顺序(这两个操作可以是同一个线程,也可以是跨线程)。因此,JMM可以通过 happens-before 关系向开发人员提供跨线程的内存可见性保证。


《JSR-133:Java Memory Model and Thread Specification》对happens-before关系的定义如下。

  1. 如果一个操作A happens-before 另一个操作B,那么操作A的执行结果将对操作B可见,而且操作A的执行顺序排在操作B之前。

  2. 两个操作之间存在 happens-before 关系,并不意味着Java平台的具体实现必须要按照 happens-before关系指定的顺序来执行。如果重排序之后的执行结果,与按 happens-before 关系 来执行的结果一致,那么这种重排序并不非法。


总结来说所谓的 happens-before 原则就是: JMM 提供给开发人员的一个简单易于理解的一种强内存模型,如果 操作A happens-before 操作B,那么 操作A所造成的影响(比如修改数据)对B立刻可见。同时也给编译和处理器最大的宽松进行优化的可能

3、happens-before 规则


《JSR-133:Java Memory Model and Thread Specification》定义了如下happens-before规则。

  1. 程序顺序规则: 一个线程中的每个操作,happens-before于该线程中的任意后续操作。
  2. 监视器锁规则: 对一个锁的解锁,happens-before于随后对这个锁的加锁。
  3. volatile变量规则: 对一个volatile域的写,happens-before于任意后续对这个volatile域的 读。
  4. 传递性: 如果A happens-before B,且B happens-before C,那么A happens-before C
  5. start()规则:如果线程A执行操作ThreadB.start(),那么A线程的ThreadB.start() 操作happens-before于线程B中的任意操作。
  6. join()规则:如果线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作 happens-before于线程A从ThreadB.join() 操作成功返回。

笔者注: 对于Lock锁的实现而言,其实现的 happens-before 原则的本质并不是2,而是3,这是因为在Lock的实现中,是基于 AQS 实现的,通过修改AQS中的 volatile 类型的 state 变量来达到 heppens-before 的原则。


这里我们举两个例子来说明 heppens-before 原则对于开发人员作出的强内存模型的保证。

  • 对于原则 5 而言: 我们在主线程中对一些数据操作,随后启动子线程A,那么在主线程中对于变量的操作,对于子线程A而言都是可见的,基于此我们可以不必考虑内存模型的问题。
  • 同样的对于原则 3 而言,对一个 volatile 类型的变量而言,是对于 volatile 读线程可见的,这就保证了线程A修改 volatile 变量,其他线程可以立即读取到这个 volatile 变量,这就是 JMM 提供的强内存的模型特性。

4、 总结


再次总结下: 什么是happens-before 原则? 它是 JMM 提供给开发人员的一个简单易于理解的一种强内存模型,只要操作A happens-before 操作B,那么 操作A 所造成的影响(比如修改数据)对操作B立刻可见,即使操作A和操作B不在同一个线程中,另外其也给编译和处理器最大的宽松进行优化的可能。

推荐阅读

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值