Lazy initialization 是一种设计模式,它延迟对象的创建、值的计算或其他昂贵操作,直到第一次真正需要时才执行。这种技术可以优化性能,减少不必要的资源消耗。
核心概念
延迟创建:对象只在第一次使用时才被创建
线程安全:在多线程环境下确保只初始化一次
缓存结果:初始化后的结果被保存供后续使用
应用场景
创建开销大的对象
不一定会被使用的资源
需要按需加载的数据
初始化成本高的计算
C# 实现
1. 使用 Lazy<T> 类 (.NET 4.0+)
using System;
class ExpensiveResource
{
public ExpensiveResource()
{
Console.WriteLine("ExpensiveResource created!");
}
public void UseResource()
{
Console.WriteLine("Using the resource...");
}
}
class Program
{
// 使用Lazy<T>实现延迟初始化
private static Lazy<ExpensiveResource> lazyResource = new Lazy<ExpensiveResource>(() => new ExpensiveResource());
public static ExpensiveResource Resource
{
get { return lazyResource.Value; }
}
static void Main()
{
Console.WriteLine("Program started");
// 此时资源还未创建
// 第一次访问时创建
Resource.UseResource();
// 后续访问使用已创建实例
Resource.UseResource();
}
}
2. 手动实现 (适用于旧版.NET)
class ManualLazy<T> where T : new()
{
private T _value;
private readonly object _lock = new object();
private bool _isInitialized = false;
public T Value
{
get
{
if (!_isInitialized)
{
lock (_lock)
{
if (!_isInitialized)
{
_value = new T();
_isInitialized = true;
}
}
}
return _value;
}
}
}
C++ 实现
1. 使用 std::once_flag 和 std::call_once (C++11及以上)
#include <iostream>
#include <mutex>
#include <memory>
class ExpensiveResource {
public:
ExpensiveResource() {
std::cout << "ExpensiveResource created!" << std::endl;
}
void useResource() {
std::cout << "Using the resource..." << std::endl;
}
};
class LazyResourceHolder {
private:
std::once_flag initFlag;
std::unique_ptr<ExpensiveResource> resource;
void initResource() {
resource = std::make_unique<ExpensiveResource>();
}
public:
ExpensiveResource& getResource() {
std::call_once(initFlag, &LazyResourceHolder::initResource, this);
return *resource;
}
};
int main() {
std::cout << "Program started" << std::endl;
LazyResourceHolder holder;
// 第一次访问时创建
holder.getResource().useResource();
// 后续访问使用已创建实例
holder.getResource().useResource();
return 0;
}
2. 经典双重检查锁定模式
#include <iostream>
#include <mutex>
#include <memory>
class ExpensiveResource {
public:
ExpensiveResource() {
std::cout << "ExpensiveResource created!" << std::endl;
}
};
class LazySingleton {
private:
static std::mutex mtx;
static ExpensiveResource* instance;
LazySingleton() = delete;
LazySingleton(const LazySingleton&) = delete;
LazySingleton& operator=(const LazySingleton&) = delete;
public:
static ExpensiveResource* getInstance() {
if (instance == nullptr) { // 第一次检查
std::lock_guard<std::mutex> lock(mtx);
if (instance == nullptr) { // 第二次检查
instance = new ExpensiveResource();
}
}
return instance;
}
};
// 静态成员初始化
ExpensiveResource* LazySingleton::instance = nullptr;
std::mutex LazySingleton::mtx;
int main() {
std::cout << "Program started" << std::endl;
// 第一次访问时创建
LazySingleton::getInstance();
// 后续访问使用已创建实例
LazySingleton::getInstance();
return 0;
}
线程安全考虑
C# Lazy<T>:默认是线程安全的,可以通过构造函数参数指定不同的线程安全模式
C++ std::call_once:保证初始化代码只执行一次
双重检查锁定:需要正确处理内存屏障和指令重排序问题
性能考量
延迟初始化会增加第一次访问的开销
需要额外的内存来存储初始化状态
在单线程环境中可以使用非线程安全版本提高性能
变体模式
虚拟代理:延迟加载大型对象
幽灵模式:部分初始化和按需加载
惰性求值:延迟计算直到需要结果
延迟初始化是一种强大的优化技术,但应谨慎使用,因为它可能使程序行为更难预测,并可能将初始化错误推迟到运行时才发现。