变量的延迟初始化

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/yizhenn/article/details/70244103

在绝大多数的系统中,我们都会使用正常的初始化。正常的初始化代码是这样的:

private final MyClass field = new MyCLass();

但在有些程序中,我们不希望某些变量在正常的类加载过程中就被初始化。换言之,我们希望某些变量能够延迟初始化。在阅读下面的内容之前,笔者希望你能读一下我的另外一篇文章, 单例模式的5种JAVA实现。你要问我为什么?答案只有四个字,“见多识广”!本文要介绍的内容大多采用这篇博客的技术方法。

说正题,如何实现延迟初始化呢?最开始想到的可能是酱紫:

    private MyClass field;//正确的代码
    
    public MyClass getField(){
        synchronized (this){
            if(field==null){
                field=new MyClass();
            }
        }
        return field;
    }

你肯定会问,怎么变这么复杂啦?下面这样岂不是更好?

    private MyClass field;//错误的代码

    public MyClass getField() {
        if (field == null) {
            field = new MyClass();
        }
        return field;
    }

上面是错误的代码,为何?想象下想在有两个线程并发调用getField()方法,存在这样的一个时序,可能让两个线程都执行了一遍new MyClass() 这个时候,到底随的才算是合法的初始化过程呢?很显然不对。那么正常的初始化不存在这个问题吗?恭喜你,问对地方了。答案是正常的初始化过程不存在这样的问题。因为正常的变量初始化过程是在类的<clinit>方法中进行的,而<clinit>方法是被JVM同步执行的。是不是又涨姿势了。具体的原理可以参见我的博客 JVM之类型的生命周期


那么问题就很明了了,既然人家正常的初始化流程需要同步执行,那么延迟的初始化流程也需要被同步执行。区别仅仅在于前者的同步工作是JVM来完成,而后者的同步工作需要程序员自己完成。谁让你非要延迟初始化呢!


言归正传,通过上面介绍的正确的代码,我们就可以实现延迟初始化了。但是这样的代码并发度太低,因此我们使用双重校验锁的方案。如下代码:

    private volatile MyClass field;

    public MyClass getField() {
        if(field==null) {
            synchronized (this) {
                if (field == null) {
                    field = new MyClass();
                }
            }
        }
        return field;
    }
这是双重校验锁模式的代码,为何这么写,还请读者参照 单例模式的5种JAVA实现中的双重校验锁模式,那里面讲的很明白,在此不赘述。


当然我们也可以使用静态内部类的方案来进行变量的延迟初始化。代码如下:

    private static class Holder{
        private static final MyClass field=new MyClass();
    }

    public static MyClass getField() {
        return Holder.field;
    }

好啦,介绍了3种方法来实现变量的延迟初始化。那么哪种方案好呢?


简言之,大多数的变量应该使用正常的初始化流程,而不是延迟初始化。当必须使用延迟初始化时,就可以使用对应的延迟初始化方法。对于实例域,我们使用双重校验锁来实现性能更高。对于静态域,我们使用静态内部类来实现性能更高。


笔者开设了一个知乎live,详细的介绍的JAVA从入门到精通该如何学,学什么?

提供给想深入学习和提高JAVA能力的同学,欢迎收听https://www.zhihu.com/lives/932192204248682496


猜你喜欢

转载自blog.csdn.net/yizhenn/article/details/70244103