happens-before

happens-before

JMM 的设计

JMM(Java Memory Model)——Java内存模型的设计需要考虑两个关键因素:

  1. 程序员对内存模型的使用。希望内存模型易于理解和编程。
  2. 编译器和处理器对内存模型的实现。希望内存模型对它们的束缚越少越好。
  double pi = 3.14;  // A
  double r = 1.0;  // B
  double area = pi * r * r;  // C

以上代码存在3个 happens-before 关系:
① A happens-before B
② B happens-before C
③ A happens-before C
其中②和③是必需的,但①是不必要的。

JMM 把 happens-before 要求禁止的重排序分为两类:

  1. 对于改变程序执行结果的重排序,JMM 要求编译器和处理器必须禁止。
  2. 对于不会改变程序执行结果的重排序,JMM 对编译器和处理器不做要求。

happens-before 定义

JSR-133 使用 happens-before 的概念来指定两个操作之间的执行顺序。

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

happens-before 关系本质上和 as-if-serial 语义是一回事。

  • as-if-serial 语义保证单线程内程序的执行结果不被改变,happens-before 关系保证正确同步的多线程程序的执行结果不被改变。
  • as-if-serial 语义给编写单线程程序的程序员创造了一个幻境:单线程程序是按程序的顺序来执行的。happens-before 关系给编写正确同步的多线程程序的程序员创造了一个幻境:正确同步的多线程程序是按 happens-before 指定的顺序来执行的。

as-if-serial 语义和 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() 操作成功返回。

happens-before
①happens-before②和③happens-before④由程序顺序规则产生,②happens-before③由volatile变量规则产生,根据传递性,将有①happens-before④,即线程A对volatile变量的写对线程B对volatile变量的读可见。

假设线程A在执行的过程中,通过执行 ThreadB.start() 来启动线程B,同时假设线程A在执行 ThreadB.start() 之前修改了共享变量,线程B在开始执行后会读取共享变量,对应的 happens-before 关系如图所示:

happens-before
①happens-before②由程序顺序规则产生,②happens-before④由start()规则产生,根据传递性,将有①happens-before④,即线程A在执行 ThreadB.start() 之前对共享变量所做的修改,接下来在线程B开始执行后都将确保对线程B可见。

假设线程A在执行的过程中,通过执行 ThreadB.join() 来等待线程B终止,同时假设线程B在终止之前修改了共享变量,线程A从 ThreadB.join() 返回后会读取共享变量,对应的 happens-before 关系如图所示:

happens-before
②happens-before④由join()规则产生,④happens-before⑤由程序顺序规则产生,根据传递性,将有②happens-before⑤,即线程A在执行操作 ThreadB.join() 并成功返回后,线程B中的任意操作都将对线程A可见。

参考

  1. 《Java并发编程的艺术》