
volatile 是保证了可见性还是有序性?
有序性:是因为 instance = new Singleton(); 不是原子操作。编译器存在指令重排,从而存在线程1 创建实例后(初始化未完成),线程2 判断对象不为空,但实际对象扔为空,造成错误。
可见性:是因为线程1 创建实例后还只存在自己线程的工作内存,未更新到主存。线程 2 判断对象为空,创建实例,从而存在多实例错误。
结论
主要是禁止重排序,初始化一个实例(SomeType st = new SomeType())在java字节码中会有4个步骤,
- 申请内存空间
- 初始化默认值(区别于构造器方法的初始化)
- 执行构造器方法
- 连接引用和实例
这4个步骤后两个有可能会重排序,1234 1243都有可能,造成未初始化完全的对象发布。volatile可以禁止指令重排序,从而避免这个问题。
为什么要禁止重排序?
确保先执行构造器方法,再将引用和实例连接到一起。如果没有禁止重排序,会导致另一个线程可能获取到尚未构造完成的对象。
为什么没有起到可见性的作用?
JSR-133
An unlock on a monitor happens before every subsequent lock on that same monitor
第二次非null判断是在加锁以后,则根据这一条,另一个线程一定能看到这个引用被赋值。所以即使没有volatile,依旧能保证可见性。
如果不加volatile,能不能使代码正确运行?
既然可见性已经有了保证,那我们只需要保证有序性。怎么保证有序性呢?
只需要在“构造对象”和“连接引用与实例”之间加上一道内存屏障
由于是在单线程里,同样根据JSR-133
Each action in a thread happens before every action in that thread that comes later in the program’s order