← Назад к вопросам

Как реализуешь Lazy инициализацию?

1.7 Middle🔥 171 комментариев
#SOLID и паттерны проектирования

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

# Как реализовать Lazy инициализацию

Lazy инициализация (ленивая инициализация) — это паттерн проектирования, при котором объект создаётся только тогда, когда он первый раз понадобится, а не при создании контейнера. Это экономит память и время запуска приложения.

Базовый пример: простая реализация

public class LazyInitialization {
    // Ресурс, который дорого инициализировать
    private DatabaseConnection connection;
    
    public DatabaseConnection getConnection() {
        if (connection == null) {
            connection = new DatabaseConnection();
            System.out.println("Соединение создано (ленивая инициализация)");
        }
        return connection;
    }
}

// Использование
LazyInitialization lazy = new LazyInitialization();
// Соединение ещё не создано
DatabaseConnection conn = lazy.getConnection();  // Теперь создано
DatabaseConnection conn2 = lazy.getConnection(); // Переиспользуется

Проблема: потокобезопасность

Вышеприведённый код небезопасен в многопоточной среде:

// ❌ Небезопасно в многопоточной среде
public DatabaseConnection getConnection() {
    if (connection == null) {  // Thread 1 проверяет
        connection = new DatabaseConnection();  // Thread 2 также проверяет и создаёт второй объект
    }
    return connection;
}

Решение 1: Synchronized (Double-Checked Locking)

public class ThreadSafeLazy {
    private volatile DatabaseConnection connection;
    
    // Double-checked locking паттерн
    public DatabaseConnection getConnection() {
        if (connection == null) {  // Первая проверка (быстрая, без блокировки)
            synchronized (this) {   // Блокировка только при необходимости
                if (connection == null) {  // Вторая проверка (в критической секции)
                    connection = new DatabaseConnection();
                    System.out.println("Соединение создано потокобезопасно");
                }
            }
        }
        return connection;
    }
}

Ключевой момент: ключевое слово volatile гарантирует, что все потоки видят актуальное значение переменной.

Решение 2: Eager initialization in constructor (с параметром)

public class ConfigurableLazy {
    private final DatabaseConnection connection;
    private final boolean lazy;
    
    public ConfigurableLazy(boolean lazy) {
        this.lazy = lazy;
        if (!lazy) {
            this.connection = new DatabaseConnection();
        } else {
            this.connection = null;
        }
    }
    
    public DatabaseConnection getConnection() {
        if (lazy) {
            synchronized (this) {
                if (connection == null) {
                    return new DatabaseConnection();
                }
            }
        }
        return connection;
    }
}

Решение 3: Holder паттерн (рекомендуется)

Это самый чистый способ для thread-safe ленивой инициализации:

public class LazyHolder {
    private LazyHolder() {}  // Приватный конструктор
    
    // Статический nested class инициализируется только при первом обращении
    private static class Holder {
        static final DatabaseConnection INSTANCE = new DatabaseConnection();
    }
    
    public static DatabaseConnection getInstance() {
        return Holder.INSTANCE;  // Первое обращение инициализирует Holder
    }
}

// Использование
DatabaseConnection conn = LazyHolder.getInstance();  // Инициализация здесь

Почему это работает:

  • Класс Holder инициализируется только при первом обращении к getInstance()
  • JVM гарантирует потокобезопасность инициализации класса
  • Нет необходимости в synchronized

Решение 4: Java 8+ Optional и Supplier

import java.util.Optional;
import java.util.function.Supplier;

public class LazyOptional {
    private final Supplier<DatabaseConnection> supplier;
    private Optional<DatabaseConnection> connection = Optional.empty();
    
    public LazyOptional(Supplier<DatabaseConnection> supplier) {
        this.supplier = supplier;
    }
    
    public DatabaseConnection getConnection() {
        return connection.orElseGet(() -> {
            DatabaseConnection conn = supplier.get();
            connection = Optional.of(conn);
            return conn;
        });
    }
}

// Использование
LazyOptional lazy = new LazyOptional(() -> new DatabaseConnection());
DatabaseConnection conn = lazy.getConnection();

Решение 5: Spring Lazy Bean

В Spring Framework есть встроенная поддержка:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Lazy;

@Configuration
public class DatabaseConfig {
    @Bean
    @Lazy  // Бин создаётся только при первом обращении
    public DatabaseConnection databaseConnection() {
        System.out.println("Инициализация подключения");
        return new DatabaseConnection();
    }
}

// Использование
@Service
public class UserService {
    @Autowired
    @Lazy  // Инъекция прокси, настоящий бин создаётся при первом использовании
    private DatabaseConnection connection;
    
    public void getUser(int id) {
        connection.query("SELECT * FROM users WHERE id = ?", id);
    }
}

Решение 6: Guava Supplier

import com.google.common.base.Suppliers;
import java.util.function.Supplier;

public class LazyGuava {
    private final Supplier<DatabaseConnection> memoized;
    
    public LazyGuava() {
        // Memoized suppliers вычисляют результат только один раз
        this.memoized = Suppliers.memoize(
            () -> new DatabaseConnection()
        );
    }
    
    public DatabaseConnection getConnection() {
        return memoized.get();  // Инициализируется при первом вызове
    }
}

Реальный пример: конфигурация в приложении

public class DatabaseConfig {
    private static class ConnectionHolder {
        static final DatabaseConnection INSTANCE = {
            System.out.println("Инициализация БД (ленивая)");
            return createConnection();
        };
        
        private static DatabaseConnection createConnection() {
            String url = System.getenv("DB_URL");
            String user = System.getenv("DB_USER");
            String password = System.getenv("DB_PASSWORD");
            return new DatabaseConnection(url, user, password);
        }
    }
    
    public static DatabaseConnection getInstance() {
        return ConnectionHolder.INSTANCE;
    }
}

// Использование
public class Application {
    public static void main(String[] args) {
        System.out.println("Приложение запущено");
        // БД ещё не инициализирована
        
        System.out.println("Нужна БД");
        DatabaseConnection conn = DatabaseConfig.getInstance();  // Только здесь
        // Выведет: "Инициализация БД (ленивая)"
    }
}

Плюсы и минусы

Плюсы

✅ Экономия памяти — объекты создаются только при необходимости ✅ Ускоренный запуск приложения ✅ Подходит для дорогостоящих ресурсов (БД, сетевые соединения) ✅ Может улучшить performance для редко используемых объектов

Минусы

❌ Первый доступ может быть медленнее ❌ Сложнее отследить ошибки (они могут проявиться при первом использовании) ❌ Требует потокобезопасности ❌ Может затруднить прогноз стоимости по времени

Когда использовать

Используй Lazy инициализацию для:

  • Дорогостоящих объектов (БД, HTTP клиенты)
  • Объектов, которые используются не всегда
  • В высоконагруженных приложениях для оптимизации памяти
  • Конфигураций, зависящих от environment

Избегай, если:

  • Объект используется всегда и сразу
  • Нужна предсказуемая производительность
  • Простое приложение с малым количеством объектов

Тестирование

@Test
public void testLazyInitialization() {
    LazyHolder lazy = new LazyHolder();
    
    // Первый вызов инициализирует
    DatabaseConnection conn1 = LazyHolder.getInstance();
    assertNotNull(conn1);
    
    // Второй вызов возвращает тот же объект
    DatabaseConnection conn2 = LazyHolder.getInstance();
    assertSame(conn1, conn2);
}

Заключение

Для production Java приложений лучший выбор — Holder паттерн за его простоту, потокобезопасность и отсутствие необходимости в synchronized. Spring beans с @Lazy — отличный выбор для приложений на Spring.

Как реализуешь Lazy инициализацию? | PrepBro