掌握了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
关系,在实现中也不一定按照这个顺序发生。重新排序后的顺序只要能产生合法的结果,就可以接受。
It should be noted that the presence of a happens-before relationship between two actions does not necessarily imply that they have to take place in that order in an implementation. If the reordering produces results consistent with a legal execution, it is not illegal.
注意,这里说的是“合法”的结果,并不代表这个“合法”的结果符合你的预期。
更确切的说,两个action如果有happens-before
关系,对于和它们没有happens-before
关系的代码来说,它们不一定按照这个顺序发生。
举个例子,对于同时访问数据的两个线程来说,一个线程里的写操作在另一个线程里的读操作看来,有可能是乱序的。
More specifically, if two actions share a happens-before relationship, they do not necessarily have to appear to have happened in that order to any code with which they do not share a happens-before relationship. Writes in one thread that are in a data race with reads in another thread may, for example, appear to occur out of order to those reads.
举个例子来看一下吧。假设两个线程X和Y能共同访问两个变量A和B,A和B初始值为0。
在X线程中执行
A=5
B=5
在Y线程中同时读取A和B(实际上java中没法同时原子性的读取两个变量,我们可以先读取B,再读取A),那么有没有可能读取到B=5,A=0呢?直觉上来看,是不可能的。因为X先更新A,后更新B,如果B都读取到了5,那A应该也是5才对。
但是java内存模型明确指出,这种情况是有可能的。因为某个编译器认为
A=5
B=5
和
B=5
A=5
也没有什么区别嘛,所以先执行哪个也没关系,所以大刀阔斧的调换了顺序。
所以为了得到需要的结果,在编程时需要建立正确的happens-before
关系。建立的方法,可以参考java语言规范。
比如java语言规范就规定了对volatile
字段的写入,happens-before
后续对该字段的读取。happens-before
关系确定以后,不仅让volatile
字段的值让所有线程立即可见,还限制了对该字段访问操作的reorder。
具体可以参考如下对volatile关键字的解释。
http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#volatile