九、线程安全的单例模式

2019-10-24  本文已影响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底层包含了三个动作:

解决方法?

加一个关键字即可: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;
    }

}

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

上一篇下一篇

猜你喜欢

热点阅读