JMM与happens-before

happens-before是JMM最核心的概念,理解happens-before是理解JMM的关键。程序员

一.JMM的设计算法

  首先,让咱们先分析一下JMM的设计意图。从JMM的设计者的角度,在设计JMM的时候要考虑一下两个关键因素:
  1.程序员对内存模型的使用。程序员但愿内存模型易于理解、易于编程。程序员但愿基于一个强内存模型来编写代码。编程

  2.编译器和处理器对内存模型的实现。编译器和处理器但愿内存模型对它们的束缚越少越好,这样它们就能够作尽量多的优化来提升性能。编译器和处理器但愿实现一个弱内存模型。多线程

  上述两个因素相互矛盾,因此JSR-133专家组在设计JMM时的核心目标就是找到一个好的平衡点:一方面,要为程序员提供足够强的内存可见性保证;另外一方面,对编译器和处理器的限制要尽量地放松。下面让咱们来看JSR-133是如何实现这一目标的。app

double pi = 3.14;   // A
double r = 1.0;     // B
double area = pi * r * r;  // C

  上述计算圆的面积的代码中,存在三个happens-before关系:
    A happens-before B分布式

    B happens-before C性能

    A happens-before C优化

  在者三个happens-before关系中2和3是必须的,1是没必要要的。所以JMM把happens-before要求禁止的重排序分了下面两类spa

  1.会改变程序执行结果的重排序线程

  2.不会改变程序执行结果的重排序

  JMM对这两种不一样性质的重排序,采用了不一样的策略,以下:
  1.对于会改变程序执行结果的重排序,JMM要求编译器和处理器必须禁止这种重排序

  2.对于不会改变程序执行结果的重排序,JMM对编译器和处理器不作要求(JMM容许这种重排序)

  下图是JMM的设计示意图

  

  JMM向程序员提供的happens-before规则能知足程序员的要求,JMM的happens-before规则不但简单易懂,并且也向程序员提供了足够强的内存可见性保证。

  JMM对编译器和处理器的束缚已经尽量少。从上面的分析能够看出,JMM实际上是在遵循一个基本原则:只要不改变程序的执行结果(指的是单线程程序和正确同步的多线程程序),编译器和处理器怎么优化都行。例如,若是编译器通过细致的分析后,认定一个锁只会被单个线程访问,那么这个锁能够被消除。再如,若是编译器通过细致的分析后,认定一个volatile变量只会被单个线程访问,那么编译器能够把这个volatile变量看成一个普通变量来对待。这些优化既不会改变程序的执行结果,又能提升程序的执行效率。

二.happens-before的定义

  happens-before的概念最初由Leslie Lamport在其一篇影响深远的论文(《Time,Clocks andthe Ordering of Events in a Distributed System》)中提出。Leslie Lamport使用happens-before来定义分布式系统中事件之间的偏序关系(partial ordering)。Leslie Lamport在这篇论文中给出了一个分布式算法,该算法能够将该偏序关系扩展为某种全序关系。

  JSR-133使用happens-before的概念来指定两个操做之间的执行顺序。因为这两个操做能够在一个线程以内,也能够是在不一样线程之间。所以,JMM能够经过happens-before关系向程序员提供跨线程的内存可见性保证(若是A线程的写操做a与B线程的读操做b之间存在happensbefore关系,尽管a操做和b操做在不一样的线程中执行,但JMM向程序员保证a操做将对b操做可见)。

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

  1.若是一个操做happens-before另外一个操做,那么第一个操做的执行结果将对第二个操做可见,并且第一个操做的执行顺序排在第二个操做以前。

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

  上面1是JMM对程序员的承诺。从程序员的角度来讲,能够这样理解happens-before关系:若是A happens-before B,那么Java内存模型将向程序员保证——A操做的结果将对B可见,且A的执行顺序排在B以前。注意,这只是Java内存模型向程序员作出的保证!

  上面2是JMM对编译器和处理器冲排序的约束。MM实际上是在遵循一个基本原则:只要不改变程序的执行结果,编译器和处理器怎么优化都行。happens-before这么作的目的,都是为了在不改变程序执行结果的前提下,尽量地提升程序执行的并行度。

三.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()(启动线程B),那么A线程的ThreadB.start()操做happens-before于线程B中的任意操做

  6.join规则:若是线程A执行操做ThreadB.join()并成功返回,那么线程B中的任意操做happens-before于线程A从ThreadB.join()操做成功返回。

四.锁的内存语义

释放锁时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存中;获取锁时会把该线程对应的本地内存置为无效。

相关文章
相关标签/搜索