← Назад к вопросам
Как реализована Lazy Initialization?
2.0 Middle🔥 161 комментариев
#SOLID и паттерны проектирования
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
# Lazy Initialization
Определение
Lazy Initialization (ленивая инициализация) — это паттерн проектирования, при котором дорогостоящий объект создаётся не при инициализации, а только когда он действительно понадобится (в момент первого обращения).
Основная идея:
Обычно: создание → хранение → использование
Lazy init: хранение → использование + создание (при первом обращении)
Пример проблемы
// ПЛОХО: создаём ресурс сразу, даже если он может не понадобиться
public class DatabaseConnection {
private Connection connection;
public DatabaseConnection() {
// Дорогостоящая операция подключения
this.connection = DriverManager.getConnection(
"jdbc:postgresql://localhost/mydb", "user", "password");
System.out.println("Connection created!");
}
public void query(String sql) {
// Используем connection
}
}
// Использование
DatabaseConnection db = new DatabaseConnection();
// Соединение уже создано, даже если мы никогда не будем его использовать!
// Это расходует память и других ресурсы
if (someCondition) {
db.query("SELECT * FROM users");
}
Решение 1: Простой Lazy Initialization
public class DatabaseConnection {
private Connection connection = null; // null изначально
public void query(String sql) throws SQLException {
// Создаём соединение при первом использовании
if (connection == null) {
connection = DriverManager.getConnection(
"jdbc:postgresql://localhost/mydb", "user", "password");
System.out.println("Connection created!");
}
Statement stmt = connection.createStatement();
stmt.executeQuery(sql);
}
}
// Использование
DatabaseConnection db = new DatabaseConnection();
// Соединение ещё не создано
db.query("SELECT * FROM users");
// Соединение создаётся только здесь, при первом обращении
Решение 2: Thread-safe Lazy Initialization
В многопоточной среде нужно быть осторожнее:
Вариант 1: Synchronized метод
public class ThreadSafeDatabaseConnection {
private Connection connection;
public synchronized Connection getConnection() throws SQLException {
if (connection == null) {
connection = DriverManager.getConnection(
"jdbc:postgresql://localhost/mydb", "user", "password");
System.out.println("Connection created!");
}
return connection;
}
public void query(String sql) throws SQLException {
Connection conn = getConnection();
Statement stmt = conn.createStatement();
stmt.executeQuery(sql);
}
}
Минусы:
- Synchronized на весь метод может быть медленным
- Даже после инициализации все потоки ждут lock
Вариант 2: Double-Checked Locking (оптимизация)
public class OptimizedDatabaseConnection {
private volatile Connection connection; // volatile!
public Connection getConnection() throws SQLException {
// Первая проверка без lock (быстро)
if (connection == null) {
synchronized (this) {
// Вторая проверка с lock (безопасно)
if (connection == null) {
connection = DriverManager.getConnection(
"jdbc:postgresql://localhost/mydb", "user", "password");
System.out.println("Connection created!");
}
}
}
return connection;
}
}
Как это работает:
- Первая проверка без lock (O(1))
- Если null, входим в synchronized
- Вторая проверка уже с lock (безопасно)
- Создаём объект если ещё не создан
volatile гарантирует visibility для всех потоков.
Вариант 3: Holder Pattern (самый чистый)
public class HolderDatabaseConnection {
private HolderDatabaseConnection() {}
// Вложенный класс для хранения соединения
private static class ConnectionHolder {
private static Connection connection;
static {
try {
connection = DriverManager.getConnection(
"jdbc:postgresql://localhost/mydb", "user", "password");
System.out.println("Connection created!");
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
public static Connection getConnection() {
return ConnectionHolder.connection;
}
}
// Использование
Connection conn = HolderDatabaseConnection.getConnection();
// Соединение создаётся только при первом обращении
Преимущества:
- Классы загружаются JVM при первом обращении
- Thread-safe по умолчанию (JVM гарантирует)
- Никакой явной синхронизации
- Очень быстро
Решение 3: Java 8 Supplier Pattern
public class SupplierLazyInit<T> {
private T value;
private Supplier<T> supplier;
public SupplierLazyInit(Supplier<T> supplier) {
this.supplier = supplier;
}
public T get() {
if (value == null) {
synchronized (this) {
if (value == null) {
value = supplier.get();
}
}
}
return value;
}
}
// Использование
SupplierLazyInit<Connection> connLazy = new SupplierLazyInit<>(() -> {
try {
return DriverManager.getConnection(
"jdbc:postgresql://localhost/mydb", "user", "password");
} catch (SQLException e) {
throw new RuntimeException(e);
}
});
// Использование
Connection conn = connLazy.get(); // Создаётся при первом get()
Решение 4: Java 10+ Optional Lazy Init
public class OptionalLazyInit<T> {
private Optional<T> value = Optional.empty();
private Supplier<T> supplier;
public OptionalLazyInit(Supplier<T> supplier) {
this.supplier = supplier;
}
public T get() {
if (!value.isPresent()) {
synchronized (this) {
if (!value.isPresent()) {
value = Optional.of(supplier.get());
}
}
}
return value.get();
}
}
Реальный пример: Singleton с Lazy Init
// Плохо: не thread-safe
public class DatabaseSingleton1 {
private static DatabaseSingleton1 instance;
private DatabaseSingleton1() {}
public static DatabaseSingleton1 getInstance() {
if (instance == null) { // RACE CONDITION!
instance = new DatabaseSingleton1();
}
return instance;
}
}
// Хорошо: thread-safe с double-checked locking
public class DatabaseSingleton2 {
private static volatile DatabaseSingleton2 instance;
private DatabaseSingleton2() {}
public static DatabaseSingleton2 getInstance() {
if (instance == null) {
synchronized (DatabaseSingleton2.class) {
if (instance == null) {
instance = new DatabaseSingleton2();
}
}
}
return instance;
}
}
// Лучше: используй enum (thread-safe по умолчанию)
public enum DatabaseSingleton3 {
INSTANCE;
private Connection connection;
DatabaseSingleton3() {
try {
this.connection = DriverManager.getConnection(
"jdbc:postgresql://localhost/mydb", "user", "password");
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public Connection getConnection() {
return connection;
}
}
// Использование
Connection conn = DatabaseSingleton3.INSTANCE.getConnection();
Spring Framework: Lazy Initialization
В Spring бины по умолчанию инициализируются eager, но можно сделать lazy:
@Configuration
public class AppConfig {
@Bean
@Lazy // Инициализируется при первом обращении
public DatabaseConnection databaseConnection() {
return new DatabaseConnection();
}
}
@Component
@Lazy
public class ExpensiveService {
// Создаётся только когда впервые затребуется
}
// Использование
@Autowired
private ObjectProvider<ExpensiveService> serviceProvider; // ObjectProvider поддерживает Lazy
public void doSomething() {
ExpensiveService service = serviceProvider.getIfAvailable();
// service инициализируется только здесь
}
Практический пример: HTTP Client
public class ApiClient {
private HttpClient httpClient;
// Ленивая инициализация HTTP клиента
public HttpClient getHttpClient() {
if (httpClient == null) {
synchronized (this) {
if (httpClient == null) {
httpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.build();
System.out.println("HttpClient created");
}
}
}
return httpClient;
}
public String fetchData(String url) throws IOException, InterruptedException {
HttpRequest request = HttpRequest.newBuilder(URI.create(url)).build();
HttpResponse<String> response = getHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
return response.body();
}
}
Когда использовать Lazy Initialization
✅ Используй, когда:
- Объект дорогостоящий (DB соединение, HTTP клиент)
- Объект может не понадобиться
- Время инициализации критично
- Есть много таких объектов
❌ Не используй, когда:
- Объект дешёвый для создания
- Объект всегда нужен
- Нужна простота кода
- Гарантированно используется при запуске
Производительность
long start = System.currentTimeMillis();
DatabaseConnection db = new DatabaseConnection();
long elapsed = System.currentTimeMillis() - start;
// Без lazy: 500ms (соединение создано)
// С lazy: 1ms (ничего не создано)
db.query("SELECT ...");
// Первый запрос: 500ms + время запроса
// Последующие: время запроса
Заключение
Lazy Initialization — это паттерн для отложенного создания дорогостоящих объектов. Основные подходы:
- Простой — проверка null в getter
- Thread-safe — synchronized + volatile
- Оптимизированный — double-checked locking
- Идеальный — Holder pattern или enum singleton
- Фреймворки — Spring @Lazy, ObjectProvider
Выбирай подход в зависимости от требований к производительности и потокобезопасности.