双重校验单例模式实现:
public class Singleton {
public static volatile Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) { // 拒绝掉当对象不为空的时候剩余的线程
synchronized (instance) {
if (instance == null) { // 处理 同时进来两个线程的情况
instance = new Singleton();
}
}
}
return instance;
}
}
以上代码看起来不需要volatile修饰instance就可以保证线程安全,但是考虑重排序和可见性问题后,没有volatile是不能保证线程安全的。
public class Singleton {
private static Singleton uniqueInstance;
private Singleton(){
}
public static Singleton getInstance(){
if(uniqueInstance == null){ //#1
synchronized(Singleton.class){ //#2
if(uniqueInstance == null){ //#3
uniqueInstance = new Singleton(); //#4
System.out.println(Thread.currentThread().getName() + ": uniqueInstance is initalized..."); //#5.1
} else {
System.out.println(Thread.currentThread().getName() + ": uniqueInstance is not null now..."); //#5.2
}
}
}
return uniqueInstance;
}
}
问题一:创建多个实例(缓存导致的问题)
- thread2进入#1, 这时子线程的uniqueInstance都是为空的,thread2让出CPU资源给thread3
- thread3进入#1, 这时子线程的uniqueInstance都是为空的, thread3让出CPO资源给thread2
- thread2会依次执行#2,#3,#4, #5.1,最终在thread2里面实例化了uniqueInstance。thread2执行完毕让出CPO资源给thread3
- thread3接着#1跑下去,跑到#3的时候,由于#1里面拿到的uniqueInstance还是空(并没有及时从thread2里面拿到最新的),所以thread3仍然会执行#4,#5.1
- 最后在thread2和thread3都实例化了uniqueInstance
问题二:空指针(重排序导致的问题)
对象实例化的步骤:
- 分配内存空间。
- 初始化对象。
- 将内存空间的地址赋值给对应的引用。
实际中2和3可能会调换执行顺序

volatile能够有效的防止指令重排,实现的原理是内存屏障
内存屏障的功能:
它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;
它会强制将对缓存的修改操作立即写入主存;
如果是写操作,它会导致其他CPU中对应的缓存行无效。
网友评论