指令重排序

java 在编译程序以及CPU执行指令时为了提高执行效率,可能会对程序指令进行重排序。其中包括三种情况

1)java编译器在编译过程中,在不改变单线程语义(执行结果)的前提下,会对程序指令进行重排序。比如 程序里写了 int a=1;int b=2; 这两行在编译的过程中,由于改变顺序不会影响执行结果,所以可能会颠倒 两行的执行顺序。

2)现代CPU 通过指令级并行技术将多条指令重叠执行,如果不存在两条指令不存在数据依赖性那么也可能会被重排序。

3)多核处理器时代,处理器都是用缓存和写缓冲区,使得多线程下代码执行顺序本身就具有不确定性。

java如何保证结果的准确性

这些重排序可能会导致执行的结果和预期不一致,那么在此背景下,java如何保证 代码执行结果的准确性呢?

1)JAVA编译器本身会禁止特性类型的重排序,比如加锁避免重排序。

2)对于CPU指令重排序java在编译时会在特定位置插入内存屏障,以此来禁止特定类型的处理器重排序。

不同的处理器其实java作为跨平台的语言,在编译过程中就会通过指定的内存屏障,解决准确性的问题,而不需要程序员做过多的考虑。程序员只需要按照并发同步机制同步代码即可。

四种内存屏障:(这里理解的不是太清楚做个摘抄吧)

java并发编程(4)-指令重排序 以及java如何保证执行结果的正确性_指令

as-if-serial语义
as-if-serial是一种约定,(书中写了很多其实)就是说单线程的执行结果不能因为指令重排序而被更改。编译器CPU等必须要遵守。就是JMM已经帮我们保证 了这一点,我们编程人员不用操心。

顺序一致性内存模型
顺序一致性内存模型是一个被计算机科学家理想化了的理论参考模型,主要有两个特性:
1)所有的代码顺序执行,不存在重排序
2)不管程序是否同步,任何一步操作都原子执行且立刻对所有线程可见,所有线程都只能看到一个单一的操作执行顺序。

在这个模型可以看做 JMM 不重排序,并且每次都直接锁总线写主内存的效果。很显然实际不可能这样么做,效率很低。
JMM规范:对于正确同步的程序 该程序的执行结果将与该程序在顺序一致性模型中的执行结果相同(即JMM已经实现了这一点)。
而 对于未同步或未正确同步的多线程程序,JMM只提供最小安全性:线程执行时读取到的值,要么是之前某个线程写入的值,要么是默认值(0,Null,False),JMM保证线程读操作读取到的值不会无中生有(Out Of Thin Air)的冒出来。而并不保证执行结果与该程序在顺序一致性模型中的执行结果一致。因为会放弃很多排序优化并且会频繁回写内存。难度极大且效率极低。

所以,只有正确同步的程序才能运行处正确的结果。

happens-before
happens-before JSR-133内存模型描述内存可见性的一种概念,是JMM最核心的概念,如果一个操作的结果对另一个(不同线程的)操作可见,那么这两个操作之间就有happens-before 关系。
需要关心的规则如下:
1)一个线程中的每个操作happens-before该线程的后续操作。
2)对于一个锁的解锁happens-before 之后对这个锁的加锁。及解锁前会将更改的数据回写到主内存。(个人理解)
3)volatile声明的变量的更改 happens-before后续对这个变量的读
4)如果A happens-before B B happens-before C 那么 A happens-before C
5)如果线程A执行线程ThreadB.start() ,那么A线程的ThreadB.start()操作 happens ThreadB中的任何操作
6)如果线程A执行线程ThreadB.join(),那么线程B 的任意操作 happens before 线程A从 ThreadB 的 join方法返回的操作。

A happens-before B 并不一定意味着A 一定先于B 执行,在不影响执行结果的前提下 CPU还是有可能会对两条执行重排序的。所以 happens-before 仅仅是JAVA为了更清晰的描述,一种直观感觉上的代码先后关系(以便程序员能更好的理解)。这并不一定是最后的执行关系。 简单来说就是 JMM从底层做了优化保证了上述几点规则。程序员按照这个规则写并发程序即可。

JMM设计需要考虑两个因素:
1)程序员易理解、易使用、轻松保证程序准确性。
2)程序执行效率(CPU执行效率)尽可能的高。
这两者之间有矛盾 如果要执行效率特别高那就不能禁用cpu的重排序等优化措施。但是完全不禁用的话程序执行准确性无法保证。所以JMM就是在这个矛盾中找个一个平衡。即能让程序员轻松使用(保证内存可见性等),又能保证执行准确性。换句话说就是JMM对程序员提供一定的便于正确编程的规则(上述的6条happensbefore规则),又在保证该规则的前提下,尽可能放宽对CPU的限制获取最大执行效率。
程序员只需要按照上述的规则进行编程,只要满足这些规则JMM就可以保证程序执行结果的额准确性。