happen-before(先行发生)原则
一、导语
刚开始接触先行发生原则的时候,我不是很能理解他的作用。现在我对它的理解就是能利用这个规则去衡量并发安全的问题,之后的文章我会再总结线程安全的各种问题。
相信很多人写代码都会有这么一个经历,就是在思考自己写的代码的一个运行的顺序问题,而之前我们也接触了volatile关键字,我们知道他是可以禁止指令重排序的,因为java内存模型对volatile关键字变量定义了一些特殊的规则。但如果java代码运行的时候都是按照volatile与synchronized关键字来限制,就会造成很繁琐的现状,然而现实生活中我们在编写并发代码的时候没有感受到这些操作的繁琐,那是因为java本身自带了先行发生原则。
二、概念
先行发生原则:这是java内存模型定义的两个操作之间的顺序关系,比如:
线程1
int i=0;//操作A
线程2
j=i;//操作B
显而易见,操作A会先于操作B发生,也可以这么理解,操作A产生的影响可以被操作B观察到,私以为这个影响可以表达为结果,操作A是改变了变量,发送了消息,调用了方法等,被B 观察到了,获得了这个结果。
三、八个规则
1.程序次序规则
这个规则在理解起来最为简单了,像是我们写程序的顺序一样,先写的操作先执行。但其实准确的来说,应该是控制流的顺序而不是程序代码的顺序,因为还要考虑分支循环等结构。
控制流: 是指按一定的顺序排列程序元素来决定程序执行的顺序。Visual BASIC、C和其他编程语言也继承了控制流,语句按照出现在程序中的顺序执行。
2.管程锁定规则
一个unlock操作会先行发生与后面的lock操作
3.volatile变量规则
对带有volatile关键字的变量的读操作会先于对它的写操作
4.线程启动规则
Thread对象里的start方法会先于发生于此线程中的每一个动作。
5.线程终止规则
线程中的所有操作都会先行于对此线程的终止检测。
6.线程中断规则
对线程的interrupt()方法的调用会先行于被中断线程的代码检测到中断事件的发生(如采用Thread.interrupted()方法检测中断的是否发生)
7.对象终结规则
一个对象的初始化(构造函数的执行结束)先行于它的finnalize()方法的开始。
8.传递性规则
如果操作A先发生与操作B,操作B先行于操作C,那么操作A先行于操作C。
四、作用
判断数据是否存在竞争,线程是否安全,解决并发环境下两个操作之间是否可能存在冲突的所有问题。
五、例子
在上面理解概念的时候,我们看到两个线程的执行。
线程1
int i=0;//操作A
线程2
j=i;//操作B
线程3
i=2;//操作C
那么思考:j
的值会是多少?
这个答案是不确定的。
我们可以确定操作A先于操作B,操作A先于操作C,但是我们并不能确定操作C是否后与操作B,这就导致我们不确定j是0还会2。
再看一个例子
根据我们的程序次序规则,我们可以知道,操作A先于操作B,操作D先于操作E,根据管程锁定规则 操作B先于操作D,s所以我们可以知道操作A先于操作E,但是C与E的先后是我们推不出来。
六、结束语
尽管java内存模型本身具备先行发生原则,无须任何的手段就默认存在的,但是我们会发现还是会存在一些我们不可控制的结果出现,而且我们也感受到了,时间上的先后顺序与先行发生基本没啥关系。
当两个操作不在这八大规则之内,或者利用这八大规则无法控制的时候,指令就会出现重排序。