单例模式--我,我,我,还是我

小熊最近997加班写代码,身体严重透支。
没办法,转行去卖房子了。

1·懒汉模式

客户打电话给门店找小熊看房,同事就让客户等一下,他去叫小熊。

public class Singleton{
    private static Singleton singleton;
    
    private Singleton() {
    }

    public synchronized static Single newInstance() {
        if (singleton== null) {//小熊在不在
            singleton= new Singleton();//不在就把他找出来
        }
        return singleton;
    }
}

客户打电话就是一个线程,synchronized就是请客户等一下,避免还有其他客户找小熊乱了。

这就是最常见的单例模式,通过newInstance()来延迟加载,synchronized避免多线程问题。

2·饿汉模式

小熊早上9点到了门店。
同事说:有个客户9点30分要找你,你就在这等着吧!
小熊是新人呢,没办法,就等啊等,等得都没时间去吃早饭,就变成了饿熊~

public class Singleton {
    //小熊就提前等着,不管有没有人叫他
    private static Singleton singleton= new Singleton();
    
    private singleton(){}

    public static Singleton newInstance() {
    //客户来电话了,小熊出去带客户看房了,可是没吃早饭好饿呀!
        return singleton;
    }
}

在类加载的时候,最初就声明了对象,避免了多线程问题,可是考虑对象初始化的复杂程度所需要消耗的时间和没必要的内存开销,这么写也太随便了。

3·双重检查锁(懒汉模式的升级版)

懒汉模式有个问题呀,就是synchronized影响程序性能呀,每次newInstance()都有synchronized

那就想个方法,我不给方法synchronized,我先判断有没有singObj,没有,我再加个synchronized,然后再判断一次

public final class DoubleCheckedSingleton
{
    private static DoubleCheckedSingleton singObj = null;

    private DoubleCheckedSingleton(){
    }

    public static DoubleCheckedSingleton getSingleInstance(){
        if(null == singObj ) {                                //第一次检查
              Synchronized(DoubleCheckedSingleton.class){     //加锁
                     if(null == singObj)                      //第二次检查
                           singObj = new DoubleCheckedSingleton();
              }
         }
       return singObj;
    }
}

这里看似没有问题,其实我们都忽略了编译器初始化对象的过程。

singObj = new DoubleCheckedSingleton();

在编译器中,他的步骤是:
1·分配对象域(内存空间);
2·初始化对象;
3·singObj指向对象域(内存空间);

但是在大多数情况下,编译器很有可能对语句进行重排序(这种做法是为了在不改变语句意思的情况下,尽可能减少读取和存储的次数,提高效率

那它的步骤就变成了132;

如果AB线程同时去调用getSingleInstance()

编译器给A线程132的顺序创建对象,singObj指向还未完全初始化的对象域;singObj!=null,而对象初始化需要时间;

B线程同时请求的时候,第一次检查null == singObj返回false,证明有singObj,而singObj指向的对象域中的对象还未完成初始化,这时就发生了不可预知的情况(看到小熊个鬼哦!)

这一切都是编译器的重排序搞的鬼,该如何解决?

很简单,我们在声明singObj的时候添加volatile修饰符。

  • volatile修饰符指明了对象之于线程的可见性(一个线程修改对象,另一个线程是可见的,可知的)
  • 另一个作用就是它禁止了编译器对指令的重排序

在强制编译器按照123的顺序初始化对象后,B线程就会在第一次检查等待singObj的初始化,两个线程都会取到同一个对象。

可是前面说了,编译器的重排序是为了提高效率,加了volatile后,效率就有所下降;

4·懒加载静态内部类(推荐

public class Singleton{

    private static class SingletonHolder{ 
        public final static Singleton instance = new Singleton();
    }

    public static Singleton getInstance(){
        return SingletonHolder.instance;
    }
}

静态内部类在最初就会被初始化,同时创建了它的内存空间,而其中的对象会在调用的时候被加载(懒加载)

有点类似饿汉模式,只是使用了静态内部类的方法让单例变得线程安全。。。

你可能感兴趣的:(单例模式--我,我,我,还是我)