安全发布对象的安全性都来自于JMM提供的保证,而造成不正确的发布原因,就是在”发布一个共享对象”与”另一个线程访问该对象”之间缺少一种Happen-Before排序.
不安全的发布
package safe_unsafe_publication;
/**
* Created by fang on 2017/11/24.
* 不安全发布,一个线程正在初始化,另一个线程可能读到未构造完全的对象.
*/
public class UnsafeLazyInitialization {
private static Resource resource;
public static Resource getInstance(){
if(resource == null){
resource = new Resource();
}
return resource;
}
}
上述代码中的不安全性有两个方面
1 资源竞争问题
类的静态变量存在方法区,resource类变量(“地址变量”),被线程A 和线程B共享,但是当线程A写入resource地址变量值的时候,可能在cpu寄存器(cpu高速缓存)还未同步到cpu主存中(每一个线程都有自己的cpu高速缓存,在高速缓存中互相看不到).
导致线程B在判断resource仍然为null,线程B可能也会new Resource()操作. 这就导致了资源的竞争,也就是(查看–修改–写入)并非是原子性操作.
2 类的实例对象构造不完整,或构造错误问题.
线程A在实行getInstance()方法的时候,发现resource为null,然后new Resource(),然后设置为指向resource.
然后线程B在判断resource是否为null时,假设此时线程A中的类变量resource已经同步到CPU主存,B发现类变量resource并不为null,然后就获取resource地址,直接使用线程A new出来的对象.
.B可能使用A创建的Resource对象并不完整,或者创建时错误的.(线程B看到A线程的执行顺序,和A真正的执行顺序可能不同,JVM重排列).
(以上是自己的理解,与类的加载过程有关系,在加载 连接阶段已经有了类的地址变量,但下一个阶段才是对象的初始化阶段. 也就是在方法区已经存在对象实例的地址对象变量,但是堆中的实例对象并没有初始化完整.)
官方说明:
除了不可变对象外,使用被另一个线程初始化的对象通常是不安全的,除非对象的发布操作是在使用该对象的线程开始使用之前执行.
安全发布
安全初始化模式
如下代码
package safe_unsafe_publication;
/**
* Created by fang on 2017/11/24.
* 安全的延迟加载
*/
public class SafeL