懒汉单例的不安全性

2019-04-02  本文已影响0人  做梦枯岛醒

1. 普通

简单的懒汉模式如下:

public class Lazy {
    private static Lazy lazy;

    public static Lazy getInstance(){
        if(lazy == null){
            lazy = new Lazy();
        }
        return lazy;
    }
}

懒汉方式进行初始化的步骤就是在Jvm进行类加载的时候不进行对象创建,直到调用者调用getInstance()的时候才返回一个新的实例。

这样做的好处很显然,刚才也有提到,JVM会进行延迟加载,不至于在初始化的时候完成很多操作。

但是又有一个很显然的问题诞生了,那就是线程不安全

考虑下面这样一个情景,当线程A与B同时访问getInstance()方法,假设A先到达,判断instance为null后准备创建对象,B也正好检查完准备创建,那么意想不到的事情就来了,A,B创建了两个不同的对象,而不是我们目标意义上的单例,由于这种线程执行的不确定性,导致这种写法是线程不安全的。

2. 优化

既然线程不安全,那么试试synchronized关键字。

public class Lazy {
    private static Lazy lazy;

    public synchronized static Lazy getInstance(){
        if(lazy == null){
            lazy = new Lazy();
        }
        return lazy;
    }
}

加上同步关键字之后,线程变得安全,但是又随之而来一个问题,就是效率问题,我们知道同步的含义,同步是一种排队等待的执行方式,所以排队等待会浪费很多的时间,在多次调用中,并不能发挥很优的效率。

3.再优化

public class Lazy {
    private static Lazy lazy;

    public static Lazy getInstance(){
        if(lazy == null){
            synchronized (Lazy.class) {
                if(lazy == null)
                    lazy = new Lazy();
            }
        }
        return lazy;
    }
}

使用同步关键字的效率低下原因在于,每个线程都要等待判断对象是否为空,但是实际上一次创建之后,不出意外后面的都是不为null的,白白浪费了很多等待时间,于是终极优化采用了同步语句块来实现同步。

通过对Lazy.class 对象加锁,来实现创建对象的同步,所以判断是否为空的过程就省去了。

但是当一群线程蜂拥而至的时候,判断lazy引用为null,就要争先恐后的去创建对象,这样当然不行,所以只能有一个精子(线程)进入卵细胞(临界区)【莫名其妙的开车】,当它进入临界区创建了一个对象后,其他的线程再进去就要判断lazy是否已经被创建。

最终就得到了一个 线程安全,延迟加载,效率又高的懒汉模式。

上一篇 下一篇

猜你喜欢

热点阅读