- 判断一个程序是不是存在数据竞争
如果一个程序中存在两个有冲突但不存在happens-before
偏序关系的操作,则称这个程序是存在数据竞争的。
【案例1】假设r1
和r2
是局部变量,A
和B
是共享变量,则
Thread 1 | Thread 2 |
---|---|
1: r2 = A;
|
3: r1 = B;
|
2: B = 1;
|
4: A = 2;
|
由于
- 操作1和操作4是存在冲突的,且不存在happens-before偏序关系;
- 操作2和操作3是存在冲突的,且不存在happens-before偏序关系;
所以这个程序是存在数据竞争的。
- 定义了一个
happens-before
一致性内存模型
对于被同步的共享变量,保证弱一致性,通过加内存屏障(Memory Barrier)和缓存一致性协议实现,具体地讲:
- 读volatile变量或者进入锁,都会保证进入一致性;
- 写volatile变量或者释放锁,都会保证释放一致性;
- 同步变量的操作不允许进行指令重排序,保证了其顺序性;
结合Pentium和PowerPC处理器中广泛使用的一种主流的缓存一致性协议--MESI来解释下。
假设CPU1执行线程A,CPU2执行线程B;CPU1和CPU2都有高速缓存;线程A和线程B共享volatile变量started,
- 当CPU1从地址started=false读取数据,会广播它的读请求,如果没有收到响应,即表明CPU1是最先开始读取started的,然后将数据存入它的缓存并置为exclusive;
- 当CPU2也从地址started读取数据时,会广播它的读请求:CPU1在收到该读请求后,检测到地址冲突,将缓存中的拷贝状态置为为shared,作为响应;CPU2收到CPU1的响应后,将数据存入它的缓存并置为shared;
- 当CPU2写started,即started=true时,将缓存中started状态置为modified,并广播其写请求;CPU1在收到请求后,将started的拷贝状态置为Invalid,作为响应;
- 当CPU1再从started读取数据时,会广播它的读请求;CPU2收到请求后,将started=true的数据发送到CPU1和刷新到主内存,设置started拷贝的状态为shared,作为响应;CPU1收到CPU2的响应后,将数据存入它的缓存并置为shared;
对于没有被同步的共享变量,
- 只保证其正确性,即从单线程执行的角度来说,程序的执行结果和程序定义的结果是一致性的;
- 不保证其顺序性,因为没有数据相关性的代码是可以重排序的;
- 判断一个程序是不是被正确地同步了
一个程序里的所有具有顺序一致性的执行都是无数据竞争的当且仅当这是一个被正确同步的程序。
根据上述定义,显然有:
- 如果一个程序被正确同步了,则就不需要考虑代码重排因素对程序的影响了;
- 正确同步一个程序可以用来防止由于代码重排导致的违反直觉的执行结果,但并不保证一个程序的正确性;
- 如果一个程序被正确同步了,则程序员就可以以一种简单的方式来考虑程序的执行了;
具体的案例请参考:
https://github.com/liyanghao/java-study/blob/master/src/concurrency/happens-before-examples.md
【参考资料】
- https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4.5
- 《聊聊高并发(三十三)Java内存模型那些事(一)从一致性(Consistency)的角度理解Java内存模型》http://blog.csdn.net/iter_zc/article/details/41943387
- 《深入理解Java内存模型(三)——顺序一致性》http://www.infoq.com/cn/articles/java-memory-model-3
- 《为什么程序员需要关心顺序一致性(Sequential Consistency)而不是Cache一致性(Cache Coherence?)》
http://www.infoq.com/cn/articles/java-memory-model-3