分析双重锁校验单例模式中的volatile为什么不可以省略。
public class Singleton_05 {
/**
* 注意此处volatile关键字不可少
*/
private static volatile Singleton_05 instance;
private Singleton_05() {
}
public static Singleton_05 getInstance(){
// 如果对象已经存在,则直接返回
if(null != instance)
return instance;
// 否则新建对象,也要进行非空判断,防止多线程情况下,多个线程都判断空对象后准备创建对象
synchronized (Singleton_05.class){
if (null == instance){
instance = new Singleton_05();
}
}
return instance;
}
}
以上的程序保证这个类的对象是单例的,有且仅有一个,但是如果没有加volatile关键字,则可能出现问题,原因在于指令重排。所谓指令重排是指在不影响程序运行结果的情况下,改变程序语句的执行顺序,减少中断,提高执行效率。对象创建过程中涉及三条指定,分别是给对象分配空间,赋值(先赋初值),建立关联。假设3条指定分别为a,b,c,在多线程情况下,如果一个线程过来,判断对象为空,则新建对象,a->b->c,完成对象创建,其他线程再过来的时候,如果在对象创建完成之前,则对象为空,执行创建,由于创建的程序加了锁,所以等待锁释放;如果在对象完成之后过来,则对象不为空,直接返回,由此,就实现了单例。
但是,存在问题,由于synchronized不保证有序性,所以如果第一个线程创建对象的时候,发生了指令重排,在对象赋值之前建立了关联,即a->c->b,则会发生问题,此时另外一个线程过来,发现对象不为空,直接返回,但是此时返回的对象是错误的,未赋值的。
解决上述问题的方法就是加volatile关键字,volatile可以保证内存可见性,禁止重排序,底层原理是在生成字节码的时候,在相关语句的前后加入内存屏障,具体可以另行了解。