由浅入深单例模式详解

单例模式第一版:懒汉模式

public class Singleton {
    private Singleton(){}//私有构造函数
    private static Singleton instance=null;//单例对象
    public static Singleton getInstance(){
    //静态工厂方法
        if (instance==null){
            instance=new Singleton();
        }

        return instance;
    }
}

如果单例初始值是null,还未构建,则构建单例对象并返回,这种写法属于懒汉模式。

如果单例对象一开始就被new Singleton()主动构建,则不需要判空操作,这种写法属于饿汉模式。

众所周知,懒汉模式是非线性安全的,饿汉模式是线性安全的。那么为什么说懒汉是不安全的呢?

(什么是线程安全呢?jvm有一个main memory,线程有自己的working memory,一个线程对一个变量进行操作的时候,都要在自己的working memory里建立一个copy,操作完之后存入main memory.多个线程同时操作同一个变量,就可能会出现不可预知的结果。线程安全就是说多线程访问同一代码,不会产生不确定的结果。)

假设,当Singleton类刚被初始化,instance对象还是null,这时候有两个线程同时访问getInstance(),

因为instance=null,所以两个线程都通过了if条件判断,开始执行new Singleton(),结果就是instance被构建了两次,结果充满了不确定性,不能保证是唯一单例。

饿汉模式优化方案:

public class Singleton {
    private Singleton(){}//私有构造函数
    private static Singleton instance=null; //单例对象
    public static Singleton getInstance(){
        if (instance==null){//双重检测机制
            synchronized (Singleton.class){//同步锁
                if (instance==null){//双重检测机制
                    instance=new Singleton();
                }
            }
        }
        return instance;
    }
}

1.为了防止new Singleton()被多次执行,因此在new之前使用Synchronized同步锁,锁住整个类

2.当两个线程同时访问,进入Synchronized内之后,如果线程A构建玩对象,也就是已经执行完 instance=new Singleton();的时候,线程B刚好先一步通过了第一次判空验证,如果不做第二次判空的话,线程B还是会再次构建instance

经过上面的优化,表面看起来已经没有问题了。但是由于JVM编译器的指令重排机制,所以上述代码还存在一个漏洞。

指令重排是什么意思呢?比如instance = new Singleton,会被编译器编译成如下指令:

memory=allocate();//分配对象内存空间

ctorInstance(memory);//初始化对象 

instance=memory;//设置instance指向刚设置的地址

这个指令顺序不是一成不变的,有可能经过jvm和cpu的优化指令重拍成下面顺序:

memory =allocate();    //1:分配对象的内存空间 

instance =memory;     //3:设置instance指向刚分配的内存地址 

ctorInstance(memory);  //2:初始化对象 

当线程A执行完1,3,时,instance对象还未完成初始化,但已经不再指向null。此时如果线程B抢占到CPU资源,执行  if(instance == null)的结果会是false,从而返回一个没有初始化完成的instance对象。为了解决这个问题我们可以这样做,

 private volatile static Singleton instance = null;  //单例对象

经过volatile的修饰,当线程A执行instance = new Singleton的时候,JVM执行顺序是什么样?始终保证是下面的顺序:

memory =allocate();    //1:分配对象的内存空间 

ctorInstance(memory);  //2:初始化对象 

instance =memory;     //3:设置instance指向刚分配的内存地址 

猜你喜欢

转载自blog.csdn.net/qq_34772082/article/details/84560362