单例模式的一些问题思考
public class A {
private volatile static A a;
private A(){
}
public static A getInstance(){
if(a ==null){
synchronized (A.class){
if(a==null){
a = new A();
}
}
}
return a;
}
}
1.为什么synchronized 不加在方法上?
答案:细化锁,有可能获取实例的时候会在这上面写一些业务代码,这些业务代码不需要加锁,因此尽量少的锁定同步范围。
2 为什么要两次判断a == null
1.1 第一次判断为空,是为了提高效率,如果a不为空就直接返回对象,不用进行锁的竞争,提高了效率。
1.2 第二次判断为空,假如A线程和B线程同时进入了 A == null 这个if条件里面,然后A线程获得了A.class的锁,完成了初始化,这时候A释放锁,因为A已经执行完同步代码块了,释放锁,然后B开始执行了,如果没有里面的A==null判断,也去执行一次new A(),这时候就有两个对象了,就不是单例模式了。因此需要两次为空判断。
3.为什么要加volatile 变量
不加volatile变量,要理解这个问题需要知道,new Object,这个操作分为三个指令,1 分配内存空间,2. 初始化类的属性等
3. 建立类的属性引用, 如果A线程在进行new A()这个操作的同时,发生了指令重排序,意思就是1–>2–>3的这个操作变成了3 --> 2–>1,B线程这时候进来了,进入if (A == null)的判断,因为你3先执行了,这时候A==null是false,就直接去拿到A这个对象了,但是这个对象有个问题,就是没有完全的完成类的初始化,如果你B线程取A对象的值的时候,有可能取到为空的属性值。这时候,单例模式就有问题了。因此要加上volatile变量,禁止指令重排序,从而禁止你提前取对象,总结就是一句话,加这个属性的含义是禁止你拿到半成品对象,导致业务异常。