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

九、线程安全的单例模式

作者: 一直想上树的猪 | 来源:发表于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;
    }

}

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

相关文章

  • 设计模式

    手写单例模式(线程安全) 你知道几种设计模式?单例模式是什么?Spring中怎么实现单例模式?

  • 设计模式(2) 单例模式

    单例模式 线程安全的Singleton 会破坏Singleton的情况 线程级Singleton 单例模式是几个创...

  • 面试复习-设计模式

    一、单例模式 确保一个类只有一个实例,并提供一个全局访问点。 线程不安全的单例模式 懒汉式 线程安全的单例模式: ...

  • Java 常用单例

    一、线程不安全的单例模式 二、线程安全且高效的单例模式 1.双重校验锁 2.静态内部类

  • 设计模式——单例模式

    单例模式 饿汉模式 懒汉模式 线程不安全的模式 线程安全模式 懒汉模式和饿汉模式的区别

  • 单例设计模式笔记

    记录几种单例模式写法。 饿汉模式(线程不安全) 懒汉模式(线程不安全) 懒汉锁模式(线程安全) 懒汉双重判断模式(...

  • 实现单例模式的方式2

    方式一: 能保证线程安全 定义类的时候实现单例模式 方式二: 能保证线程安全 对已定义好的类实现单例模式

  • Java 单例模式

    概述 Java中单例模式是一种常见的设计模式,单例模式总共有7种写法。 懒汉,线程不安全 懒汉,线程安全 饿汉 饿...

  • 设计模式——单例模式的破坏

    概述: 之前学习了单例模式的几种实现,解决了多线程情况下,单例的线程安全问题,保证了单例的实现。但是单例模式在下面...

  • 单例模式与多线程

    这里直接给出几种懒汉模式的单例多线程安全的写法。这种写法就是普通的单例模式,但是是非线程安全的,至于原因,根据前面...

网友评论

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

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