2-单例模式 volatile 原子性(要么都成功,要么都失败)

单例模式 volatile 原子性(要么都成功,要么都失败)

 

单例模式,顾名思义就是只有一个实例,可以分为饿汉式和懒汉式以及JDK1.5以后引入的枚举;

饿汉式

优点是当类在加载的时候就已经创建了实例,所以是安全的

缺点是当类加载的时候就已经创建并完成了实例化,没有达到lazy-loading(延迟加载)的效果,所以如果该类始终没有用到,就会造成内存的浪费

懒汉式是和懒汉式相反,这种方式实现了lazy-loading,但是有一个明显的缺点就是,这种单例只能在单线程环境下使用,在多线程环境下,一个线程刚刚通过(null == instance)语句的同时,另一个线程也通过了该语句块,那么这个时候就会产生两个实例,这样就与单例模式只有一个实例的核心思想相悖了。所以在多线程模式不可使用该种方式。

最好的方式是双层线程锁(保障安全)+volatile(禁止重排序,保证了变量修改的可见性,但不保证原子性)

那么有没有一种方式可以在不使用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包下的相关类,局限性比较大。

参考地址

https://www.cnblogs.com/dcfwow/p/10770397.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章