上了重量级的synchronized不一定万事大吉,也有可能出并发问题,跟volatile比没有强弱之分,有时候需要互补。
先看一段代码:
public class DoubleCheckedLocking {
private static DoubleCheckedLocking resource;
public static DoubleCheckedLocking getResource(){
if(resource == null){
synchronized(DoubleCheckedLocking.class){
if(resource == null) //二次check,防止lock之前的第一次check以后这段,被其他线程new了
resource = new DoubleCheckedLocking();
}
}
return resource;
}
}
修正后:
public class DoubleCheckedLocking {
// volatile 保障可见性,禁止指令重排
private static volatile DoubleCheckedLocking resource;
// 单例,private掉构造函数
private DoubleCheckedLocking(){}
public static DoubleCheckedLocking getResource(){
if(resource == null){
synchronized(DoubleCheckedLocking.class){
if(resource == null)
resource = new DoubleCheckedLocking();
}
}
return resource;
}
}
resource = new DoubleCheckedLocking() 是非原子的
分为3步,分配内存、创建对象、变量指向内存地址。但 JMM(Java 内存模型)允许编译器和处理器对不存在依赖关系的指令进行重排序。
比如可能会变成:分配内存、变量指向内存地址、创建对象。
1、线程 B 在第一个 if (resource == null) 判断时,看到 resource != null,于是直接 return resource,就会拿到一个未初始化完成的对象。
2、改了之后,加了volatile ,禁止指令重排,线程 B 在第一个 if (resource == null) 判断时,如果看到 resource != null,意味着resource一定完成了初始化,
(禁止指令重牌:分配内存、创建对象、变量指向内存地址,按序发生。非null说明最后一步也做完了)
synchronized 在这里的作用:保证不会有两个线程同时去new,即互斥。
但不保证指令不重排和这个场景下的可见性。
它的可见性是有条件的:线程B只有获取锁,才能看到之前线程A所做的修改。