掌握了Sequential Consistency一致性模型之后,我们重新审视一下java的并发。
我们已经说过,Sequential Consistency只保留进程本地顺序。上文中我们了解到,由于CPU指令重排序、内存多级缓存不一致等问题,硬件层次没有提供Sequential Consistency,需要软件开发人员实现。下面我们就来看一下,在java中如何实现Sequential Consistency。
Java中Sequential Consistency的基础,是JVM的happens-before关系。在Java Language Specification的内存模型中,规定了happens-before关系,正确处理happens-before关系,是java语言正确实现并发的基础。显而易见的是,在同一个线程的代码中,前面的action happens-before 后面的action。然而它后面又说,两个action就算有happens-before关系,在实现中也不一定按照这个顺序发生。重新排序后的顺序只要能产生合法的结果,就可以接受。
注意,这里说的是“合法”的结果,并不代表这个“合法”的结果符合你的预期。
更确切的说,两个action如果有happens-before关系,对于和它们没有happens-before关系的代码来说,它们不一定按照这个顺序发生。
举个例子,对于同时访问数据的两个线程来说,一个线程里的写操作在另一个线程里的读操作看来,有可能是
举个例子来看一下吧。假设两个线程X和Y能共同访问两个变量A和B,A和B初始值为0。
在X线程中执行
在Y线程中同时读取A和B(实际上java中没法同时原子性的读取两个变量,我们可以先读取B,再读取A),那么有没有可能读取到B=5,A=0呢?直觉上来看,是不可能的。因为X先更新A,后更新B,如果B都读取到了5,那A应该也是5才对。
但是java内存模型明确指出,这种情况是有可能的。因为某个编译器认为
也没有什么区别嘛,所以先执行哪个也没关系,所以大刀阔斧的调换了顺序。
所以为了得到需要的结果,在编程时需要建立正确的happens-before关系。建立的方法,可以参考java语言规范。
比如java语言规范就规定了对volatile字段的写入,happens-before后续对该字段的读取。happens-before关系确定以后,不仅让volatile字段的值让所有线程立即可见,还限制了对该字段访问操作的reorder。