欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

从另一个角度理解java并发

程序员文章站 2022-03-28 13:56:54
...

掌握了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并发
图片描述

但是java内存模型明确指出,这种情况是有可能的。因为某个编译器认为

A=5
B=5

B=5
A=5

也没有什么区别嘛,所以先执行哪个也没关系,所以大刀阔斧的调换了顺序。


从另一个角度理解java并发
图片描述

所以为了得到需要的结果,在编程时需要建立正确的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