Java 中懒加载的正确实现方式

本文探讨了懒加载的不同实现方式及其线程安全性,包括不安全的单例实现、使用synchronized关键字的安全实现、双重检查锁定的问题及解决方案、Eager初始化和Bill Pugh Singleton模式。重点介绍了每种方法的优缺点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.通常懒加载初始化的典型实现方式:

public class LazyInit {

    public static Resource resource;

    public static Resource getResource() {
        if (resource == null) {
            resource = new Resource();
        }
        return resource;
    }
}

在单线程应用程序中,以上示例保证按预期工作。但在多线程应用程序可能有各种不同的结果。它可以按预期工作,它也可能多次初始化资源,它也可能发布部分对象(读取部分对象), 所以为不安全实现方式。

2. 安全的实现方式

public class LazyInit {

    public static Resource resource;

    public synchronized static Resource getResource() {
        if (resource == null) {
            resource = new Resource();
        }
        return resource;
    }
}

现在,resource由LazyInit类保护。只有一个线程可以进入此方法并检查resource是否已初始化,如果是,则返回当前实例。

3. 在某些情况下,您可能为了加快访问Resource实例的过程采取双重检查

public class LazyInit {

    public static Resource resource;

    public static Resource getResource() {
        if (resource == null) {
            synchronized (LazyInit.class){
                if (resource == null){
                    resource = new Resource();
                }
            }
        }
        return resource;
    }
}

如果已异步初始化,则检查Resource ,否则您将同步初始化它。

看起来很好,但它不如(2.安全的实现方式)。在这种情况下,一个线程可以看到Resource部分加载,这意味着它与null不同,但它却还没有完全构造。

JSR-133表示,Java保证最终字段在构造完成之前“冻结”。我们来看看Resource文件。

public class LazyInit {
    
    public final int FINAL_VALUE;
    public int value;
    
    public Resource(){
        FINAL_VALUE = 1;
        value = 1;
    }
}

LazyInit.getResource()方法检索Resource的一个线程实际上会获得部分对象

... 
int aFinalValue = LazyInit.getResource()。FINAL_VALUE //保证1 
int aValue = LazyInit.getResource()。value //可以是0 
...

用于双重检查锁定的解决方案需要易失性类型的资源。建议不要将此解决方案用于静态字段.

public class LazyInit {

    private volatile Resource resource;

    public  Resource getResource() {
        Resource result = resource;
        if (result == null) {
            synchronized (this){
                result = resource;
                if (result == null){
                    resource = new Resource();
                }
            }
        }
        return resource;
    }
}

易失性保证字段在所有线程中返回最新值并在写入时锁定它。请注意,我们将volatile值分配给普通变量,因此在检查时不知道它就不会改变, 所以将它赋值给一个临时变量。

4. 最快速初始化方式

public class EagerInit {

    private static Resource resource = new Resource();

    public static   Resource getResource() {
        return resource;
    }
}

最终字段不仅承诺在构造完成之前被“冻结”,而且静态字段也必须在构造函数触发之前初始化。

该示例保证在使用resource之前实例化Resource.

上面的示例是线程安全的,但它是由JVM立即初始化的,并不是我们想要的。我们可以使用这些知识,但这并不是我们想要的。

5. Bill Pugh Singleton(单列模式)

事实证明,JVM对内部静态类的处理方式不同。它们保持隐藏,直到实际使用时才初始化实例对象。这个小信息驱使我们进入线程安全单例初始化的下一个例子

public class LazyInit {

    private static class Holder{
        public static Resource resource = new Resource();
    }
    
    public static Resource getResource(){
        return Holder.resource;
    }
}

这是Java中最推荐的延迟初始化方法。超级简单,超级安全。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值