首先看一个面试题目,多线程环境下调用下面两个方法,最终执行完毕后,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修饰,避免重排序即可; |