JAVA 内存模型与happens before 一致性

1,169 阅读7分钟

定义

根据JLS定义。JAVA内存模型描述了一些规则,这些规则用于判断一个程序的执行轨迹是否合法,怎么判断呢?利用JMM描述的规则来考察执行轨迹中的读动作并且检查这些读动作观察到的写是否合法。 简单来说就是:JMM用来分析程序中访问内存操作的合法性。

A memory model describes, given a program and an execution trace of that program, whether the execution trace is a legal execution of the program. The Java programming language memory model works by examining each read in an execution trace and checking that the write observed by that read is valid according to certain rules.

The memory model describes possible behaviors of a program. An implementation is free to produce any code it likes, as long as all resulting executions of a program produce a result that can be predicted by the memory model.

考察并发程序的表现时,无需关注重排序细节,无需关注物理平台,只需要凭借JMM提供给我们的happens-befor工具即可!JMM本来就旨在方便程序员屏蔽掉各种繁琐的细节,写出并发安全的程序。

JAVA线程与内存

每一个线程都只能看到自己的内存

单线程下的安全保证:as-if-serial 语义

不管怎么重排序,单线程程序的执行结果不能被改变,编译器和处理器重排序时遵守as-if-serial语义

程序顺序Program Order

线程t的程序顺序是指线程t的动作的一个全序,该全序遵从线程t的线程内语义 (intra-thread semantics)。换言之,t的动作可以任意被编译器重排序,只要这个重排序的结果不影响程序的执行结果,就好像这些动作是严格按照代码顺序执行的那样。

人话:程序顺序是线程t的动作的一个排序,这个排序不一定和代码顺序相同。只要不影响执行的结果。那么这些动作可以被任意排序。因此一个代码他的程序顺序可能有多个。

同步顺序 Synchronizaation顺序

同步顺序是一个执行序列的同步动作的全序关系 (执行序列(Execution)定义见下)

对于每个线程t,同步动作的同步顺序与程序顺序一致

同步synchronized-with关系是同步动作的 偏序关系 。下列规则产生了同步关系。

  1. 一个监视器的解锁动作 synchronizes-with 后续对于该监视器的锁定 (后续的定义根据同步顺序判断,下同)
  2. 一个volatile变量的写入动作 synchronizes-with 后续对该变量的读
  3. 开启一个线程的动作 synchronizes-with 该线程的第一个动作
  4. 对每个变量的写入默认值 synchronizeds-with 每个线程的第一个动作
  5. 线程T1的最后一个动作 synchronizes-with 检测线程T1是否终止的线程T2的任何动作(可通过T1.isAlive(),T1.join()检测)
  6. 如果线程T1中断了线程T2, 那么T1实施的中断动作 synchronizes-with 线程T2的察觉到中断的动作(抛出中断异常/调用Thread.interrupted/Thread.isInterrupted)

同步关系产生了同步边,同步边的左侧(源)叫做释放, 同步边的右侧(目的地)叫做获取

注意区分 同步关系 与 同步顺序,同步顺序仅仅只是就一个执行序列的同步动作排了个先后顺序。他们是一种全序关系,而同步关系是一种偏序关系,同步关系的产生需要遵从一些规则。利用同步关系,我们可以产生happens-before关系。

happens before 顺序

如果动作x,y符合下述规则,则动作x,y之间有happens before关系

  • 程序顺序规则:一个线程中的动作x,y. 程序顺序中如果x在y前面。那么x happens before y
  • synchronizes with规则: x synchronizes with y 意味着 x happens before y
  • 传递性:如果A happens- before B,且B happens- before C,那么A happens- before C。
  • 对象的构造器的结尾 happens before 析构器 的开始

x happens before y 描述的是动作x对于动作y可见,而不是中x 在y 之前被执行。

Conflicting Access ()

程序被正确同步当且仅当程序的执行没有数据竞争。

一个线程读共享变量,一个线程写共享变量,会导致存在数据竞争。我们可以借助happens-before关系来同步程序来消除数据竞争。

正确同步的顺序遵从顺序一致性。

程序中的竞争访问被happens-before关系排序 -> 不存在数据竞争-> 程序被正确同步 -> 遵从顺序一致性

happens-before 一致性

一个动作集具有happens-before一致性,如果这个动作集中的读r,W(r)是读r能看到的所有写动作。那么hb(r, W(r))为假。并且,不存在写w使得 w.v = r.v 同时hb(W(r), w) ,hb(w, r)为真。

在一个具有happens-before一致性的动作集中,每一个读都会看到不被happens before关系阻止的写。

happens before 的最大用处在于:!!!如果hb(w,r)。那么r能看到w的写入

x happens before y并不代表 x 在y之前被执行。只代表x 的结果对y可见。 例如:考虑单线程下程序中动作x,y。x,y依次读取2个无关变量。我们可以说x hb y.也可以说y hb x。都可以。因为他们都不影响单线程语义。可以被任意重排序。 但是,如果x写入变量a,y 读取变量a,如果程序代码中顺序x 在y之前。那么单线程语义中x 是必须得在y之前执行的。不能被重排序。所以我们有x hb y。而没有y hb x。

Executions 执行序列

一个执行序列是一个元组 < P, A, po, so, W, V, sw, hb >, 由下列元素组成。

P - a program

A - a set of actions

po - program order, which for each thread t, is a total order over all actions performed by t in A

so - synchronization order, which is a total order over all synchronization actions in A

W - a write-seen function, which for each read r in A, gives W(r), the write action seen by r in E.

V - a value-written function, which for each write w in A, gives V(w), the value written by w in E.

sw - synchronizes-with, a partial order over synchronization actions

hb - happens-before, a partial order over actions

Note that the synchronizes-with and happens-before elements are uniquely determined by the other components of an execution and the rules for well-formed executions (§17.4.7).

An execution is happens-before consistent if its set of actions is happens-before consistent

应用题

  1. 例1:
Thread 1            Thread 2
1 r2 = A;            3  r1 = B;
2 B = 1;             4  A = 2;

问题:r1,r2可能的值有哪些?

答:我们有hb(1,2), hb(3,4),(这里2 hb1, 4hb3也可以不影响结果,下同)r2 读取A的值,可能读取到A初始值0或者 语句4对A的写入2。

r1读取B的值,可能读取到初始值0或者语句2对B的写入1.

因此可能的取值有(1,0),(1,2),(0,0),(0,2)

  1. 例2: obj是线程1,2之间共享的一个对象
Thread 1             Thread 2
--------             --------
1 r2 = A;              5 r1 = B;
2 monitorenter obj     6 monitorenter obj
3 monitorexit obj      7 monitorexit obj
4 B = 1;               8 A = 2;

问题:r1,r2可能的取值有哪些?

答:同步顺序不确定,我们可能有hb(3, 6)或者hb(7,2).

情况1: hb(3,6)即 线程1锁的释放动作早于线程2加锁动作。

由于hb(3,6) , hb(6,8), hb(1,3) 所以hb(1,8) 所以r2读取A 无法获取到语句8对A的写入2.因此r2只能取默认值0.

语句5,4之间无Happens before关系。因此r1可以取0或者1

情况2: hb(7,2) 即线程2锁的释放动作早于线程1加锁动作

由于hb(5,7), hb(7,2),hb(2,4)所以hb(5,4)因此r1只能取默认值0.而语句1,8无Happens before关系。r2可以取0或者2.

综上,可能值有(0,0),(1,0),(0,2)

Ref

  1. cloud.tencent.com/developer/a…
  2. www.cnblogs.com/flyingrun/p…
  3. stackoverflow.com/questions/4…
  4. stackoverflow.com/questions/3…
  5. stackoverflow.com/questions/3…
  6. stackoverflow.com/questions/3…
  7. stackoverflow.com/questions/1…