[讨论] 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
不错,学习了
|