懒汉单例优化

多线程下的懒汉单例模式(加synchronized关键字)

public class Singleton {
    private static Singleton instance;
    private Singleton() {}
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

效率低,调用getInstance的方法时都需要同步

 

优化1

public class Singleton {
    private static Singleton instance;
    private Singleton() {}
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                instance = new Singleton();
            }
        }
        return instance;
    }
}

如果没有该实例,只需要在创建该实例的代码上添加synchronized代码块即可,若该实例已经存在,直接return该实例即可。

但是该种方式根本不能起到线程同步的作用,因为由于实例化对象时,内存对象会进行重排序,

就有可能会导致多线程的时候执行到if判断的时候还没被初始化或者得到一个不是null但是还未初始化完成的对象。

public class Singleton {
    private static volatile Singleton instance;
    private Singleton() {}
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

优缺点分析:这种Double-Check-Lock的方式进行了两次实例是否为空的判断,因为volatile 关键字就指定了禁止重排序,volatile保证了变量修改的可见性,但不保证原子性。

一个线程对变量的修改,另一个线程能立即读到这个修改后的值,volatile是遵循happens-before原则的,这样多线程环境下,进行if判断的时候,能得到一个完整的已经实例好的对象,

别的线程进行if判断的时候,直接返回该对象即可。这样我们就可以实现线程安全了,并且该种方式也实现了lazy-loading,效率高。

那么有没有一种方式可以在不使用lock、synchronized的方式下实现线程安全的单例模式呢?答案是,有的,那就是使用CAS。

CAS是什么呢?Compare And Swap,顾名思义就是比较和交换。CAS是项乐观锁技术,其包含三个参数,分别为V(待更新的值)、E(期望值)、N(新值),

当V和E不相同时,说明其他线程已经做过更新了,此时该线程不执行更新操作,或者再次尝试读取V值再次尝试修改该值,也可以选择放弃该操作。若是V和E相等,则当前线程可以修改V值,

也就是执行CAS操作。CAS操作中没有锁的参与,但是针对其他线程针对共享资源的操作做了处理。由于CAS中没有锁的参与,所以针对线程共享资源的操作也不会发生死锁了,可以说CAS天生免疫死锁。

public class Singleton {
    private static final AtomicReference<Singleton> INSTANCE = new AtomicReference<Singleton>();
    private Singleton(){}
    public static Singleton getInstance(){
        for(;;){//不限次数的自旋循环,如果CAS一直失败,CPU的执行开销被消耗很严重
            Singleton singleton = INSTANCE.get();
            if(null != singleton){
                return singleton;
            }
            singleton = new Singleton();
            if(INSTANCE.compareAndSet(null, singleton)){//当前实例为null,才替换当前实例为singleton
                return singleton;
            }
        }
    }
}

缺点分析:该方式使用CAS实现线程安全,实现相比传统的锁机制来说,CAS依靠的是底层硬件(CPU的CAS指令)来实现的,不需要进行频繁的线程切换和阻塞而造成资源的额外消耗。

但是这种方式还是有缺点的,CAS的自旋循环如果长时间不成功,则会给CPU带来非常大的执行开销。另外一点就是如果N个线程同时执行到singleton=new Singleton()的时候,则会同时创建大量的实例,很有可能发生OOM。

CAS的缺点:首先CAS的ABA问题,这个可以通过添加版本号或时间戳来解决,在比较完内存中的值以后,再比较时间戳或者版本号是否一致。

CAS的自旋操作,如果CAS长期不成功,会一直重试,会严重增加CPU的执行开销。JDK1.6以后默认开启了自旋(--XX:+UseSpinning),

可以通过JVM设置CAS的自旋操作次数来解决(-XX:PreBlockSpin=10,JVM的默认自旋次数是10),当超过指定次数后,自动失败退出。还有一种自适应自旋锁,自旋的时间不再固定,

会根据前一次同一个锁上的自旋时间以及锁的拥有者的状态来决定的。

CAS的功能的局限性,CAS只能保证单个内存中的值的原子性,在java中原子性不一定能保证线程安全,还需要volatile保证有序性来实现线程安全。

在需要保证多个内存中的值的情况下,CAS也无能为力,可以看情况使用悲观锁。所以说在并发冲突概率比较高的环境中,尽量不要使用CAS。

其次CAS的核心是依靠可以直接调用底层资源的Unsafe类的CompareAndSwap()方法实现的,在java使用只能使用Atomic包下的相关类,局限性比较大。

posted @ 2019-08-29 15:21  何浩源  阅读(206)  评论(0编辑  收藏  举报
//一下两个链接最好自己保存下来,再上传到自己的博客园的“文件”选项中