一.单例模式第一版:懒汉模式
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指向刚分配的内存地址
静态内部类优雅单例模式
由于静态内部类只加载一次,所以可以用这个特性来实现单例,这个方式比(double check+volatile)更优雅
public class SingleInstance {
public static SingleInstance getInstance(){
return InnerClass.singleInstance;
}
private static class InnerClass{
private static SingleInstance singleInstance = new SingleInstance()
}
}
优势:
- 兼顾了懒汉模式的内存优化以及饿汉模式的安全性。
劣势:
- 需要两个类去做到这一点,虽然不会创建静态内部类的对象,但是其 Class 对象还是会被创建,而且是属于永久带的对象。
- 创建的单例,一旦在后期被销毁,不能重新创建。