最近在读《深入理解Java虚拟机》,学习到Java内存模型时遇到一个问题。书上12.3.3节在讲volatile可以防止指令重排序时有下面一段描述:
使用volatile修饰的变量,赋值后(前面
mov %aex, 0x150
(%esi) 这句便是赋值操作)多执行了一个lock addl $0x0
操作,这个操作相当于一个内存屏障,只有一个CPU访问内存时,并不需要内存屏障;但如果多个CPU访问同一块内存,且其中有一个在观测另一个,就需要内存屏障来保证一致性了。
指令重拍在单处理器上也存在,如果存在多线程并发应该也会导致一致性问题,为什么书上说“只有一个CPU访问内存时,并不需要内存屏障”呢?
因为程序在运行时内存实际的访问顺序和程序代码编写的访问顺序不一定一致,这就是内存乱序访问。内存乱序访问行为出现的理由是为了提升程序运行时的性能。
我们通过volatitle关键字来分析。
volatitle关键字两个作用,指令重排和线程可见。比如在java.util.current包中,volatitle关键字出现的频率就非常高。多线程的情况下,在某些时候,指令重排会导致线程不安全,比如我们new一个对象,其实在jvm中是分三步执行的: Object o = new Object();
如果o没有用volatitle声明,那么执行顺序并不能保证是1-2-3.有可能是1-3-2,那么如果在多线程的情况下,线程1初始化到3的时候,线程2获得时间片,这个时候 o 是不为null 但是还没有调用构造方法初始化。
通过new一个对象来说,就解释了多线程情况下,必要的指令重排是必须的。但是在单线程的情况下,无论如何重排都不会影响到。也就不再需要内存屏障。
内存屏障指令仅仅直接控制CPU与其缓存之间,CPU与其准备将数据写入主存或者写入等待读取、预测指令执行的缓冲中的写缓冲之间的相互操作。
指令重排,可以提高程序的执行速度,所以肯定会在处理器中存在