单例模式
date: 2020-09-28 16:09:00
updated: 2020-09-28 17:42:00
单例模式
1. 饿汉式
public class EHan {
private static EHan instance = new EHan();
private EHan(){}
public static EHan getInstance(){
return instance;
}
}
优点:没有线程安全问题
缺点:在初始化时就创建好了,浪费内存空间
2. 懒汉式
2.1 线程不安全
public class LHan {
private static LHan instance;
private LHan(){}
public static LHan getInstance(){
if(instance == null){
instance = new LHan();
}
return instance;
}
}
优点:只有当用的时候才检查是否有实例,没有才创建
缺点:有线程安全与不安全两种,区别在于是否有 synchronized 关键字
2.2 线程安全
public class LHan {
private static LHan instance;
private LHan(){}
public static synchronized LHan getInstance(){
if(instance == null){
instance = new LHan();
}
return instance;
}
}
3. DCL
由于线程安全的懒汉式的 synchronized 是加在方法上的,如果该方法里还有其他的一些代码,会降低执行效率,加锁的粒度太粗,所以可以进而改写下面这个
3.1 懒汉式优化后的一种写法
public class LHan {
private static LHan instance;
private LHan(){}
public static LHan getInstance(){
if(instance == null){
synchronized(this){
instance = new LHan();
}
}
return instance;
}
}
但是这种写法也存在一定问题,当线程A在判断instancenull后停住了,此时还没有创建实例;线程B抢到了资源,发现instancenull,也会进入到代码块,于是A和B都会创建一个实例。
3.2 DCL 单例模式
Double Check Lock 两次检查,中间插入一个lock
public class LHan {
private static volatile LHan instance; // 必须要加 volatile
private LHan(){}
public static LHan getInstance(){
if(instance == null){ // 这一层判断是为了提高效率。锁竞争很耗费时间和效率,避免多个线程每一次getInstance都要进入到同步代码块
synchronized(this){
if(instance == null){
instance = new LHan();
}
}
}
return instance;
}
}
有可能会存在,A在第一层判断结束后停住,B进入同步代码块,new一个对象实例,进行一定操作后,把instance置为null,然后A进入同步代码块,会再次new一个对象。解决:添加版本号。
4. 静态内部类
public class Singleton {
private static class SingletionHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton(){}
public static final Singleton getInstance(){
return SingletionHolder.INSTANCE;
}
}
静态内部类的方式效果类似双检锁,但实现更简单。但这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。