[讨论] java并发可见性问题

freish 2012-11-06
可见性问题是个很微妙的问题,很久之前就接触到了happens-before,但JLS中的描述过于正规,很难理解其表达的确切含义,阅读了很多关于happens-before相关的资料,一直不得法,直到遇到了这篇文章:http://www.cs.umd.edu/class/fall2010/cmsc433/lectures/happens-before.txt,虽然不是针对java的,比较通俗的讲解了happens-before,很容易理解。

结合JLS 第17章反复的阅读了几次,将自己对happens-before的一点理解写在下面:

(一)inter-thread Action有以下几种
1、非volatile读
2、非volatile写
3、synchronization action,包括
a、volatile读
b、volatile写
c、锁定monitor
d、解锁monitor
e、线程中的首个和最后一个action
f、启动一个线程或检测线程是否终止

(二)intra-thread语义
所谓intra-thread语义就是单个线程执行某条代码路径的时候,不管是否存在重排序等操作,在执行结果上都会表现出与源码一致的结果。比如这个代码路径中有一个写入变量v的操作o1,只要中间没有再写该变量,后续就能读到之前o1写的值。

(三)Programs and Program Order
program order意为线程t执行的所有的inter-thread Action都表现出了intra-thread的语义。
每次对变量v的读操作r都能看到写操作w写入v的值,只要存在下面的条件:
·执行顺序上w在r之前;
·且不存在这样写操作w',执行顺序上w在w'之前,w'在r之前。

Program Order是顺序一致(Sequential consistency)的。顺序一致性是可见性的强有力保证。

(四)Synchronization Order
每个线程t中synchronization action的synchronization order是与Program Order一致的。
Synchronization action之间的synchronized-with关系有如下几种:
1、解锁monitor m synchronized-with 后续 锁定monitor m;
2、volatile写变量v synchronized-with 后续对该变量的 volatile读;
3、启动一个线程 synchronizes-with 线程中的首个action;
4、变量写入默认值 synchronizes-with 线程中的首个action;
5、线程t1中的最后一个action  synchronizes-with 线程t2中检测t1是否终止的action(如t1.isAlive() 或 t1.join());
6、若t1中断t2,该中断 synchronizes-with 任意时刻其它线程(包括t2)发现t2被中断了(通过抛出InterruptedException或调用 Thread.interrupted、Thread.isInterrupted)。


(五)Happens-before Order
两个action可以被 happens-before排序,如果一个action happens-before另外一个,则第一个对第二个是可见的(JLS JAVASE7版17.4.5原文是这么说的: If one action
happens-before another, then the first is visible to and ordered before the second.)
用hb(x,y)表示x happens-before y。
1、(规范原文)If x and y are actions of the same thread and x comes before y in program order,then hb(x, y).
   (个人理解)Program Order中执行顺序上w在r之前,则有hb(w,r)。也就是说,如果没有w',hb(w,w')且hb(w',r),则w写入的最新值r一定是可见的。(这个w'的条件下面就不再赘述了)
2、(规范原文)There is a happens-before edge from the end of a constructor of an object to the start of a finalizer (§12.6) for that object.
   (个人理解)构造方法的结束 happens-before finalizer。也就是说构造方法中对字段的写操作的最新值在finalize中一定是可见的。
3、(规范原文)If an action x synchronizes-with a following action y, then we also have hb(x, y).
   (个人理解)形如写入volatile变量v,其它线程读取该变量一定能看到最新值。
4、(规范原文)If hb(x, y) and hb(y, z), then hb(x, z).
   (个人理解)hb关系是可传递的。
  
--------------------以上都是废话,下面正式开始--------------------




例1、有非volatile变量v和volatile变量v1
t1          t2
a:写入v    c:读取v1
b:写入v1   d:读取v
根据最新的volatile语义,a与b不会重排序;根据Program Order有hb(a,b),hb(c,d);根据Synchronization Order有hb(b,c),根据hb的传递性,有hb(a,d),也就是说如果没有synchronized,a写入v的值,若不存在w'写入v,d中是可以获取到a写的最新值的。


例2、非volatile变量v
t1             t2
a:lock m      x:lock m
b:写入v       y:读取v
c:unlock m    z:unlock m
同理根据Program Order有hb(b,c),hb(x,y),根据Synchronization Order有hb(c,x),根据hb传递性有hb(b,y),b写入的最新值y中能看到。

例3、非volatile变量v
t1             t2
a:写入v       x:lock m
b:lock m      y:unlock m
c:unlock m    z:读取v
这是一个空同步块,假设没有被编译器优化掉,根据Program Order有hb(a,c),hb(x,z),根据Synchronization Order有hb(c,x),根据hb传递性有hb(a,z),a写入的值(未必是最新,但终会看到)z中能看到。


数据争用
包含两个或以上没有happens-before关系的冲突访问(当两个线程同时访问某个变量,且至少一个是写操作,也就是说写写、写读是冲突访问)时,就说存在数据争用。


一个程序是正确同步的当且仅当不存在数据争用(A program is correctly synchronized if and only if all sequentially consistent executions are free of data races)。

在例3中,对v的写操作是存在数据争用的,但是只要t1线程在unlock m之前,t2没有去读取v,就能确定t2读取的v是最新值。如果有多个线程写入v,总有一个线程会覆盖其它线程的值,但总能保证t2最终能取到内存中的最新值。

最后,happens-before关系只保证可见性,不保证其它,比如原子性。



以上对于例1,2,3的理解有没有问题呢?还有数据争用的危害或者说后果是什么?
blizzard213 2012-11-06
例1 2 3看着怎像是和t1/t2执行顺序相关呢
freish 2012-11-07
blizzard213 写道
例1 2 3看着怎像是和t1/t2执行顺序相关呢


实际中与执行顺序是有关的,但在谈及可见性的时候,指的是如果变量v被线程t1改变了,线程t2是否读到改变的值,这就不用涉及实际的执行顺序了
miroku 2012-12-05
1和2是可以看到的,3的话不确定。volatile关键字在实现的时候应用了2条汇编语句,一是内存屏障,使得屏障之后的操作不会乱序到屏障之前,2是强制将修改后的值刷回主存。而加锁那个,保证了最后离开临界区的时候v的值是刷回主存的,而且临界区内的指令也不会被乱序。3的话。。。感觉因为你的临界区是没有指令的所以有可能和没有这个是一样的,所以不确定。以上是我的认识,请各位大神拍砖
lvwenwen 2012-12-06
不错,学习了
Global site tag (gtag.js) - Google Analytics