Java基础: 什么是指令重排序/as-if-serial/内存屏障/happens-before

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/lovewebeye/article/details/79728688

Java基础知识

指令重排序

在执行程序时,为了提高性能,编译器和处理器会对指令做重排序。

  1. 编译器优化重排序:编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
  2. 指令级并行的重排序:如果不存l在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
  3. 内存系统的重排序:处理器使用缓存和读写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。

但是,可以通过插入特定类型的Memory Barrier来禁止特定类型的编译器重排序和处理器重排序。

数据依赖性

如果两个操作访问同一个变量,其中一个为写操作,此时这两个操作之间存在数据依赖性。 
编译器和处理器不会改变存在数据依赖性关系的两个操作的执行顺序,即不会重排序。

as-if-serial

不管怎么重排序,单线程下的执行结果不能被改变。

编译器、runtime和处理器都必须遵守as-if-serial语义。

内存屏障(Memory Barrier )

上面讲到了,通过内存屏障可以禁止特定类型处理器的重排序,从而让程序按我们预想的流程去执行。

内存屏障,又称内存栅栏,是一个CPU指令,基本上它是一条这样的指令:

  1. 保证特定操作的执行顺序。
  2. 影响某些数据(或则是某条指令的执行结果)的内存可见性。

编译器和CPU能够重排序指令,保证最终相同的结果,尝试优化性能。插入一条Memory Barrier会告诉编译器和CPU:不管什么指令都不能和这条Memory Barrier指令重排序。

Memory Barrier所做的另外一件事是强制刷出各种CPU cache,如一个Write-Barrier(写入屏障)将刷出所有在Barrier之前写入 cache 的数据,因此,任何CPU上的线程都能读取到这些数据的最新版本。

java内存模型volatile是基于Memory Barrier实现的。

如果一个变量是volatile修饰的,JMM会在写入这个字段之后插进一个Write-Barrier指令,并在读这个字段之前插入一个Read-Barrier指令。这意味着,如果写入一个volatile变量,就可以保证:

  1. 一个线程写入变量a后,任何线程访问该变量都会拿到最新值。
  2. 在写入变量a之前的写入操作,其更新的数据对于其他线程也是可见的。因为Memory Barrier会刷出cache中的所有先前的写入。

Happens-Before

在JMM中,如果一个操作的执行结果需要对另一个操作可见,那么这两个操作之间必须要存在happens-before关系,这个的两个操作既可以在同一个线程,也可以在不同的两个线程中。

我们需要关注的happens-before规则如下: (发生在...之前)

  1. 程序顺序规则:一个线程中的每个操作,happens-before于该线程中任意的后续操作。
  2. 监视器锁规则:对一个锁的解锁操作,happens-before于随后对这个锁的加锁操作。
  3. volatile域规则:对一个volatile域的写操作,happens-before于任意线程后续对这个volatile域的读。
  4. 传递性规则:如果 A happens-before B,且 B happens-before C,那么A happens-before C。

注意:两个操作之间具有happens-before关系,并不意味前一个操作必须要在后一个操作之前执行!仅仅要求前一个操作的执行结果,对于后一个操作是可见的,且前一个操作按顺序排在后一个操作之前。

展开阅读全文

java内存模型,happens-before指令重排序

11-09

最近在学习java并发方面的知识,关于“happens-before”与“重排序”有些疑问。rn资料1:http://blog.csdn.net/zhushuai1221/article/details/51491578 这篇文章里提到:volatile会禁止CPU对指令进行重排序rn资料2:http://ifeve.com/easy-happens-before/rn资料3:http://www.w2bc.com/article/110564 这篇文章倒数第二段有个结论:因此,“一个操作happen—before另一个操作”并不代表“一个操作时间上先发生于另一个操作”。rn看例子:rn[code=java]class Example rn int a = 0; rn CopyOnWriteArrayList list = new CopyOnWriteArrayList(); rn rn //in thread A rn public void writer() rn a = 1; //p1操作rn list.set(0,"t"); //p2操作rn rn rn //in thread B rn public void reader() rn list.get(0); //p3操作rn int b = a; //p4操作rn rn [/code]rn可能的执行时序1:rn[code=text]线程A 线程Brn p1:a = 1 rn p2:list.set(0,"t") rn p3:list.get(0)rn p4:int b = a;[/code]可能的执行时序2:rn[code=text]线程A 线程Brn p1:a = 1 rn p3:list.get(2)rn p2:list.set(1,"t") rn p4:int b = a;[/code]rn在“可能的执行时序1”中,p1,p2是同一个线程中的,p3,p4是同一个线程中的,所以有hb(p1,p2),hb(p3,p4)(p1 happens-before p2,p3 happens-before p4);由于CopyOnWriteArrayList能保证set操作happens-before(hb) get操作,且hb关系具有传递性,所以hb(p1,p4) 因此可以保证线程2中的b的值是1。rnrn我的疑问:rn疑问1:单线程情况下,由于p1与p2操作并不存在数据依赖与逻辑依赖,并且list内部的volatile变量“看不见”Example类的a变量(相关知识见资料1),因此p1与p2单线程下可能会发生重排序导致先执行p2再执行p1。这个分析是正确的吗?rn疑问2:什么情况下某个a操作后执行于某个b操作却能保证“a happens-before b”?请重新举个例子,因为资料3里的说的看不太明白。或者说:rn【在A、B线程同时执行的情况下,由于存在关系hb(p1,p2),hb(p3,p4),只要hb(p2,p3)成立(此时hb(p1,p4)成立), 那么p1与p2(或p3与p4)肯定不会发生重排序,否则hb(p1,p4)就不成立了(难道真的存在 p2时间上比p1先执行却还能保证hb(p1,p2)成立?想不明白);只要hb(p2,p3)不成立,p1与p2(或p3与p4)随便重排序,反正也不影响程序的执行结果?】rnrn有点乱,求大神指教。。 论坛

没有更多推荐了,返回首页