目录
DCL是什么
DCL:即双重验证加锁
什么是双重验证加锁,看下面代码
public class Person {
private static Person person;
private Person() {}
public static Person getInstance() {
if (person == null) {//1
synchronized (Person.class) {//2
if (person == null) {//3
person = new Person();//4
}
}
}
return person;
}
}
不难看出,就是在单例模式下获取实例的时候两次验证之间加了锁,这样有什么好处呢?如果是多个线程同时调用getInstance()方法 ,线程A->>>到了1处,发现等于null,然后被打断了,此时线程B->>>也到了1处,但是继续执行了,加锁,然后理所当然走完了整个流程,并new出Person对象,这时轮到A了,A->>>2->>>3这时判断不为null了,所以不会new了,直接返回已经存在的实例。假如第二次的if判断去掉,那就又new了一次,就不算是单例了,所以这样做就始终保证第一次被new出来的对象不会被改变。
DCL存在什么问题
也不能说DCL存在什么问题,而应该是person不加volatile会有什么问题。
new一个对象,并不是一步完成的,而是分为多步:
1、申请内存空间,并且默认初始化(赋0值,半初始化)
2、调用构造方法,进行初始化
3、返回地址给,使person指向分配的地址空间(执行完此时person就不为null)
看起来没什么问题,但是要知道CPU是可能会乱序执行指令的,因为为了效率,CPU可能会将没有必然联系的指令重排序,即3在2之前执行了,当然对于单线程这样的操作是不会影响运行,而当有多个线程,而又在此时恰好有线程B调用了getInstance()方法,第一个if,因为person指向的空间已经不为null,所以直接返回,那毫无疑问当引用person的时候,由于没有完全初始化,就可能会产生异常或者和实际不符。那怎么解决呢,答案就是给person添加volatile。
private volatile static Person person;
volatile如何解决DCL存在的问题
volatile有两个特性:可见性和有序性
什么是可见性,即线程A修改变量x,那么对于线程B应该立即可以获取到信息x被修改,并重新读取
什么是有序性,即禁止许指令重排序
禁止重排序是怎么做到的?
答案:一是语义层次,二是内存屏障。内存屏障分为读屏障(loadload),写屏障(storestore),读写屏障(loadstore或storeload);如load,loadload,load,在进行新的读操时,加入读屏障,保证上一次的读操作完成再执行新的读操作而不会越过屏障去执行新操作。