Java–单例模式及双锁检测

Java–单例模式及双锁检测

作为开发的我们对单例模式肯定不会陌生,毕竟项目中用到它的地方相当多。
一般我们都知道单例模式有两种写法,延迟加载(又叫饿汉式),意思很好懂,只有用到的时候才创建(饿汉,只有饿了的时候才吃),非延迟加载(又叫饱汉式),不管用不用一上来就创建(不管饿不饿,上来就开吃)。

一.单例模式的两种写法

从反应时间速度来取舍,用非延迟加载

1.1.非延迟加载(饱汉式)

public class SingletonNormal {

    private SingletonNormal(){}

    private static final SingletonNormal instance = new SingletonNormal();

    public static SingletonNormal getInc(){
        return instance;
    }
}

从系统资源利用上来取舍,用延迟加载。注意多线程开发中,getInc需要加同步锁。

1.2.延迟加载(饿汉式)

public class SingletonLazy {
    private SingletonLazy() { }
    private static SingletonLazy instance;

    public synchronized static SingletonLazy getInc() {
        if (null == instance) {
            instance = new SingletonLazy();
        }
        return instance;
    }
}

看到多线程模式下的延迟加载方案,我们要思考下,都知道同步锁synchronized是一个重量锁,保证同一时刻只有一个线程在执行该代码块,如果在getInc方法中有一些耗时操作,那么多个线程频繁调用getInc方法,来进行锁的争用,将会使代码执行效率大打折扣。

二.延时加载的改进-双锁检测

针对1.2中的问题所以我们得想办法,让synchronized锁住的代码块尽量少,调用synchronized锁住的代码库次数少两个方面来提高多线程开发中getInc的效率。
代码如下:

public class SingletonDoubleCheck {

    private SingletonDoubleCheck() { }

    private static volatile SingletonDoubleCheck instance;//代码1

    public static SingletonDoubleCheck getInc() {
        if (null == instance) {//代码2
            synchronized (SingletonDoubleCheck.class) {
                if (null == instance) {//代码3
                    instance = new SingletonDoubleCheck();//代码4
                }
            }
        }
        return instance;
    }
}

貌似已经改进完成,这段代码在JDK1.5以下会有问题。
假设现在有线程A,和线程B两个线程。
1.线程A进入getInc方法,走到代码2处,判断为null,进入同步块。
2.线程A走到代码3处,判读为null。
3.线程A走到代码4处,执行new操作,在SingletonDoubleCheck还未实例化完成时,使intance为非null。
4.线程B进入getInc方法,走到代码2处,判读非null,返回。
这时候线程B代码中inatance实例引用的是一个不完整的对象。
原因就在于对象的new操作并不是一个原子性操作,它分为3步:

1,给 instance 分配内存

2,调用 SingletonDoubleCheck 的构造函数来初始化成员变量

3,将SingletonDoubleCheck对象指向分配的内存空间(执行完这步 instance 就为非 null 了)

由于JVM的指令重排序(happen-before)的原因,指令重排序这个点的知识比较复杂,网上也有很多资料,大家可以自行去搜索。

所以上述线程B拿到的是一个未被初始化完成的instance,在使用时肯定会出错。

上述1.2代码片段中 代码1处的volatile关键字

摘自《深入理解Java虚拟机》

volatile屏蔽指令重排序的语义在JDK1.5中才被完全修复,此前的JDK中即使将变量声明为volatile也仍然不能完全避免重排序所导致的问题(主要是volatile变量前后的代码仍然存在重排序问题),这点也是在JDK1.5之前的Java中无法安全使用DCL(双锁检测)来实现单例模式的原因。

    原文作者:java锁
    原文地址: https://blog.csdn.net/mumawen/article/details/81087369
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞