美文网首页
九、线程安全的单例模式

九、线程安全的单例模式

作者: 一直想上树的猪 | 来源:发表于2019-10-24 15:46 被阅读0次

    一、用双重检查锁定来创建单例,它真的是安全的吗?(懒汉)

    单例模式就是我在一个应用程序中某一个类只有一个单例。将其构造方法私有化,不让外面的调用者调用其构造方法。
    双重检查

    /**
     * 懒汉式-双重检查
     * 线程不安全的实现
     */
    public class SingleDcl {
        private static SingleDcl singleDcl;
        //私有化
        private SingleDcl(){
        }
    
        public static SingleDcl getInstance(){
            if (singleDcl == null){ //第一次检查,不加锁
                System.out.println(Thread.currentThread()+" is null");
                synchronized(SingleDcl.class){ //加锁
                    if (singleDcl == null){ //第二次检查,加锁情况下
                        System.out.println(Thread.currentThread()+" is null");
                        //内存中分配空间  1
                        //空间初始化 2
                        //把这个空间的地址给我们的引用  3
                        //指令的重排序
                        singleDcl = new SingleDcl();
                    }
                }
            }
            return singleDcl;
        }
    }
    

    这样,对外提供一个getInstance()方法,外部调用者即可获取到这个对象的实例。考虑到多线程的场景下,先去检查这个对象有没有被创建出来,如果对象为null,则对这个类进行加锁(保证只有一个线程可以进入)。进入这个线程之后,再做第二次检查,如果还没有产生实例,则new出一个对象的实例,将这个实例返回出去。
    但是这种实现是一种不安全的实现
    加锁之后为什么要做第二次检查?它担心在我进入这个代码块之前,已经有一个线程先进入了,因为CPU切换时间片将线程唤起是需要时间的,检查完如果确实没有创建实例,则证明我是第一个拿到这把锁的,顺其自然地去创建这个对象。
    JVM底层
    new关键字创建对象的时候,在JVM底层包含了三个动作:

    • 1.内存中分配空间,在堆中分配一个内存空间
    • 2.空间初始化
    • 3.将这个空间的地址给对象的引用
      而在jvm底层,有一个指令的重排序。在实际的运行过程中很有可能第二步和第三步交换顺序。
      我们的检查if (singleDcl == null)就是去检查当前的引用有没有指向内存中的地址。一旦发生了指令重排序的话,这个对象的引用指向的就是一个null的空间,由于没有进行初始化,如果当前线程要使用这个对象的成员变量的话,就会报空指针异常。

    解决方法?

    加一个关键字即可:volatile
    在底层,volatile关键字可以抑制指令的重排序。保证线程拿到这个对象的时候一定是一个初始化完成了的。

    二、饿汉式

    声明这个对象的时候,就将对象初始化好。

    /**
     * 饿汉式
     *
     */
    public class SingleEHan {
        private SingleEHan(){}
        public static SingleEHan singleDcl = new SingleEHan();
    
    }
    

    为什么这种情况线程安全?
    因为加入了static关键字,由虚拟机替我们进行加锁,可以保证只有一个线程执行类加载,是由虚拟机的类加载机制保证的。

    三、懒汉---延迟初始化占位类模式

    /**
     * 懒汉式-延迟初始化占位类模式
     */
    public class SingleInit {
     /**
     * 懒汉式-延迟初始化占位类模式
     */
    public class SingleInit {
        private SingleInit(){}
    
        private static class InstanceHolder{
            private static SingleInit instance = new SingleInit();
        }
    
        public static SingleInit getInstance(){
            return InstanceHolder.instance;
        }
    
    }
    

    定义一个静态的内部类,里面有个静态变量去实例化对象。实例化的操作是在这个类的内部有一个内部类去实现的。

    相关文章

      网友评论

          本文标题:九、线程安全的单例模式

          本文链接:https://www.haomeiwen.com/subject/asqcmctx.html