为什么要用双重校验锁实现单例模式?
单例实现有饿汉模式与懒汉模式,懒汉模式能够延迟加载,使用较多,但是懒汉模式在多线程下会出现问题,即有可能会产生多个实例。
下面是懒汉模式实现的单例模式的代码
public class SingleTon{
//静态实例变量
private static SingleTone instance;
//无参构造函数
private SingleTon(){}
//获取单例的公共方法
public static synchronized SingleTon getInstance(){
if(instance == null){
instance = new SingleTon();
}
return instance;
}
}
下面是双重校验锁实现的额单例模式,极度保证线程安全
public class SingleTon{
//静态实例变量
private volatile static SingleTone instance;
//无参构造函数
private SingleTon(){}
//获取单例的公共方法
public static SingleTon getInstance(){
//先判断对象是否实例化过,如果没有实例化才进入加锁阶段
if(instance == null){//第一重校验
synchronized(SingleTon.class){
if(instance == null){//第二重校验
instance = new SingleTon();
}
}
}
return instance;
}
}
以下引用一位博主的解释,原文链接https://blog.csdn.net/weixin_39309402/article/details/106421795
-
第一次判断是否为null:
第一次判断是在Synchronized同步代码块外,理由是单例模式只会创建一个实例,并通过getInstance方法返回singleton对象,所以如果已经创建了singleton对象,就不用进入同步代码块,不用竞争锁,直接返回前面创建的实例即可,这样大大提升效率。 -
第二次判断singleton是否为null:
第二次判断原因是为了保证同步,假若线程A通过了第一次判断,进入了同步代码块,但是还未执行,线程B就进来了(线程B获得CPU时间片),线程B也通过了第一次判断(线程A并未创建实例,所以B通过了第一次判断),准备进入同步代码块,假若这个时候不判断,就会存在这种情况:线程B创建了实例,此时恰好A也获得执行时间片,如果不加以判断,那么线程A也会创建一个实例,就会造成多实例的情况。 -
所以,为了满足单例模式的要求,双重校验是必不可少的。
补充
实现线程且延迟加载还有另外一种实现方式
静态内部类实现单例模式
代码如下
public class SingleTon{
//静态内部类
private static class SingleTonHolder{
//静态常量
private static final SingleTon INSTANCE = new SingleTon();
}
//无参构造方法
private SingleTon(){}
//获取实例方法
public static final SingleTon getInstance(){
return SingleTonHolder.INSTANCE;
}
}
线程安全且延迟加载