延迟加载(Lazy Loading)

#设计模式 #延迟加载 #单例模式

什么是延迟加载?

延迟加载是一种设计思想,指推迟对象的创建直到第一次使用时才初始化。这种技术可以:

  • 减少内存占用
  • 提升应用启动速度
  • 优化资源利用

应用场景

  • 单例模式(懒汉式)
  • ORM 框架(Hibernate 懒加载)
  • 前端开发(图片懒加载)
  • Spring Bean(延迟初始化)

在单例模式中,使用延迟加载的实现方式被称为懒汉式

// 内部静态类示例
public class Dog {

    private Dog() {}

    public static Dog getDogInstance() {
        return DogFunc.INSTANCE;
    }
    private static class DogFunc {
        private static final Dog INSTANCE = new Dog();
    }

}

实现方式

延迟加载有多种实现方式:

1. 普通静态方法

通过设置静态方法检查实例是否存在来创建。

存在线程安全问题,可以通过 synchronized 解决。

public class Dog {
    private static Dog instance = null;

    public static Dog getInstance() { //同步在这里 加 synchronized
        if (instance == null) {
            instance = new Dog();
            return instance;
        }
        return instance;
    }
}

2. 内部静态类

利用内部类不会随外部类同步创建而创建的特性实现。

简单高效,同时 final 关键字保证了在创建实例时的线程安全问题

final 提供两个保证: 引用不可变:防止重新赋值

可见性保证

  • JMM(Java 内存模型)保证 final 字段初始化完成后,其他线程一定能看到正确值
  • 禁止指令重排序
  • 防止读到"半初始化"对象

3. 双重检查(DCL)

通过双重检查,第二次判断时加锁来保证线程安全。

相比直接在方法上加锁效率更高。

public class Dog {

    private static volatile Dog instance;

    public static Dog getInstance() {
        if (instance == null) {  // 第一次检查
            synchronized (Dog.class) {
                if (instance == null) {  // 第二次检查
                    instance = new Dog();  //  volatile 禁止重排序
                }
            }
        }
        return instance;
    }
}

⚠️注意点

错误 1:非静态内部类

public class Dog {
    private Dog() {}

    public static Dog getInstance() {
        return DogHolder.INSTANCE;
    }

    private class DogHolder {  //  缺少 static
        private static final Dog INSTANCE = new Dog();
    }
}

// 编译错误(Java 16-):
// error: non-static inner class cannot have static declarations

错误 2:访问点不是静态

public class Dog {
    private Dog() {}

    public Dog getInstance() {  //  缺少 static
        return DogHolder.INSTANCE;
    }

    private static class DogHolder {
        private static final Dog INSTANCE = new Dog();
    }
}

// 编译错误:
// Dog.getInstance();  // 非静态方法无法通过类名调用

错误 3:忘记 final

public class Dog {
    private Dog() {}

    public static Dog getInstance() {
        return DogHolder.INSTANCE;
    }

    private static class DogHolder {
        private static Dog INSTANCE = new Dog();  //  缺少 final
    }
}

// 问题:理论上线程安全,但违反最佳实践
// 可能被意外重新赋值

错误 4:使用反射破坏单例

// 反射可以创建新实例
Constructor<Dog> constructor = Dog.class.getDeclaredConstructor();
constructor.setAccessible(true);
Dog dog2 = constructor.newInstance();  // 新实例!

解决方案

private Dog() {
    // 防止反射攻击
    if (DogHolder.INSTANCE != null) {
        throw new IllegalStateException("单例已被创建,不允许通过反射创建新实例");
    }
}

错误 5:序列化破坏单例

// 反序列化会创建新实例
Dog dog1 = Dog.getInstance();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(dog1);

ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
Dog dog2 = (Dog) ois.readObject();  // 新实例!

解决方案

public class Dog implements Serializable {
    private static final long serialVersionUID = 1L;

    private Dog() {}

    // 实现 readResolve 方法
    private Object readResolve() {
        return DogHolder.INSTANCE;  // 返回单例实例
    }
}

Reference

设计模式:单例模式 (关于饿汉式和懒汉式)单例模式是比较常见的一种设计模式,目的是保证一个类只能有一个实例,而且自行实例 - 掘金