首先看一个面试题目,多线程环境下调用下面两个方法,最终执行完毕后,r1和r2的值可能是什么?
面试题目
首先来分析一下,多线程环境下不能确定方法执行的先后顺序,假如method1执行完之后,再执行method2;那么最终r1和r2的值是1,0; 如果method2执行完之和,method1再执行,则是0,2;
当然也会有人想到两个方法交替执行,也就是执行完第4行代码之后,执行第9行代码;那么最后的值则是0,0;
然而以上答案并不是完全正确的,还有一种情况,最终r1 r2的值可能是1和2,为什么呢?如何避免这种结果出现呢?接下来从java内存模型来分析;java内存模型
如何理解和定义java内存模型呢?
其核心是定义了特定操作的happens-before关系,用来保证两个操作的可见性(无论是单线程或者多线程);
什么是happens-before?
定义是说如果B操作再A操作之后执行,那么B操作一定可以看到A操作的结果,这样我们就说A操作happens-before B操作;
这里举一个反例可能更好理解,比如上面的题目中,方法1将b赋值为1,这个操作完成之后,对于b的读取操作,在数据没有刷新至主内存之前,那么读取操作是不能读到正确的值的。这里的写操作与读操作就没有happens before关系;
那么java中定义了哪些happen-before关系呢?
同一个线程内部,各个操作之间的关系满足happens - before;
volatile变量的写操作happens-before读操作
解锁操作happens-before加锁操作
对象构建完成,happens-before finalize操作
线程的启动,线程的操作,线程终止这三个操作由happens-before关系,具体来说就是start操作happens线程内的所有操作,线程内的操作happens-before终止操作(比如Thread.join操作)
现在来分析开篇提到的,为什么最终结果有可能是1,2呢?
显然是第5行和第10行操作发生在了第4行和第9行之前。也就是执行顺序与代码中的顺序不一致,这里很多人可能就想到是指令重排序原因导致的问题了;
也就是实际执行过程中,方法可能将两个语句顺序换了,这并没有违反java虚拟机的原则,这里要提到重排序的原则了。
java虚拟机重排序的原则
as-if-serial,也就是说在单线程环境下,排序前后的执行结果是相同的;
对于题目中的重排序是在这个原则下进行的,所以单线程下并没有什么问题;而多线程环境下会导致最终结果是1,2的问题;
如何避免出现1,2这种情况呢?
了解了happens-before原则就很简单了,将a,b两个变量用volatile修饰,避免重排序即可;