先行发生原则
前言
由上一篇,我们知道并发问题的一个原因是有序性,而java中volatile和synchronized可以保证有序性;
但是在java中,并不是所有的操作都是由volatile和synchronized实现的,java中存在”先行发生“的原则。
“先行发生”原则是判断数据是否存在竞争、线程是否安全的主要依据;
什么是先行发生原则?
先行发生原则是指:如果说操作A先行发生于操作B,也就是发生在操作B之前,操作A产生的影响能被操作B观察到。
举例说明:
//以下操作在线程A中执行
i = 1;
//以下操作在线程B中执行
j = i;
//以下操作在线程C中执行
i = 2;
假设线程A中的操作i=1先行发生于线程B的操作j=i,那么可以确定在线程B的操作执行之后,j一定等于1。因为:根据先行发生原则,i=1的结果可以被B观察到;
现在保持A先行发生于B,线程C出现在A与B之间,但是线程C与B没有先行发生关系。那么j会等于多少呢?答案至不确定。因为线程C对变量i的影响可能会被B观察到,也可能不会。因为两者之间没有先行发生关系;
====》先行发生原则就是操作A在时间上或者逻辑上比B先发生,那么B一定能看到A操作带来的影响(修改了共享变量的值等等),那么此时A就是先行发生于B。
你可能会说难道B还有可能不会看到A带来的影响吗?A操作先执行的呀!想一想我们上面提到的内存可见性和有序性…
java内存模型中的”天然“先行关系
1、程序次序规则
一个线程内,按照程序代码的顺序,书写在前面的操作先行发生于(逻辑上)书写在后面的操作。
2、管程锁定规则
一个unlock操作先行发生于后面对同一个锁的lock操作。后面指时间上的先后顺序。
3、volatile变量规则
对一个volatile变量的写操作先行发生于后面对这个变量的读操作。这里的后面指时间上的先后顺序。
4、传递性
如果操作A先行发生于操作B,操作B先行发生于操作C,那么,操作A也就先行发生于操作C。
5、线程启动规则
Thread对象的start方法先行发生于此线程的每个动作;
6、线程终止规则
线程中的所有操作都先行发生于对此线程的终止检测;
7、线程中断规则
对线程的interrupt()方法的调用先行发生于被中断线程的代码检测到中断时间的发生;
8、对象终结规则
一个对象的初始化完成先行发生于它的finalize方法的开始;
。。。。。
java无须任何手段即可保证上面的先行发生规则成立;
分析
先行发生原则(happens-before)不是描述实际操作的先后顺序,它是用来描述可见性的一种规则;
对上述中原则的理解:
对2)管程锁定规则的理解:如果线程1解锁了a,接着线程2锁定了a,那么,线程1解锁a之前的写操作都对线程2可见(线程1和线程2可以是同一个线程)。
对3)volatile变量规则的理解:如果线程1写入了volatile变量v(这里和后续的“变量”都指的是对象的字段、类字段和数组元素),接着线程2读取了v,那么,线程1写入v及之前的写操作都对线程2可见(线程1和线程2可以是同一个线程)。
总结
先行发生原则的作用:判断内存可见性与重排序是否造成并发问题。
参考:
http://ifeve.com/easy-happens-before/
http://yukai.space/2016/09/10/%E7%90%86%E8%A7%A3java%E5%86%85%E5%AD%98%E6%A8%A1%E5%9E%8B/