创建一个对象时,内存中大致经历如下三个步骤。
1.在堆中分配对象内存
2.填充对象必要信息+具体数据初始化+末位填充
3.将引用指向这个对象的堆内地址
什么是指令重排序?有两个层面:
- 在虚拟机层面,为了尽可能减少内存操作速度远慢于CPU运行速度所带来的CPU空置的影响,虚拟机会按照自己的一些规则(这规则后面再叙述)将程序编写顺序打乱——即写在后面的代码在时间顺序上可能会先执行,而写在前面的代码会后执行——以尽可能充分地利用CPU。拿上面的例子来说:假如不是a=1的操作,而是a=new byte[1024*1024](分配1M空间),那么它会运行地很慢,此时CPU是等待其执行结束呢,还是先执行下面那句flag=true呢?显然,先执行flag=true可以提前使用CPU,加快整体效率,当然这样的前提是不会产生错误(什么样的错误后面再说)。虽然这里有两种情况:后面的代码先于前面的代码开始执行;前面的代码先开始执行,但当效率较慢的时候,后面的代码开始执行并先于前面的代码执行结束。不管谁先开始,总之后面的代码在一些情况下存在先结束的可能。
- 在硬件层面,CPU会将接收到的一批指令按照其规则重排序,同样是基于CPU速度比缓存速度快的原因,和上一点的目的类似,只是硬件处理的话,每次只能在接收到的有限指令范围内重排序,而虚拟机可以在更大层面、更多指令范围内重排序。
由此可见,真正创建对象时,很有可能会进行指令重排序, 步骤如下:
1.在堆中分配对象内存
3.将引用指向这个对象的堆内地址
2.填充对象必要信息+具体数据初始化+末位填充
在这种情况下,第一重判空验证时,很有可能因为对象不为空(另一个线程执行了步骤3而未执行步骤2),直接返回一个半成品。而volatile关键字修饰的变量,禁止指令重排序,所以不会出现上述情况