Исторически ленивая инициализация появилась для оптимизации использования ресурсов, когда объект создаётся только тогда, когда он действительно нужен. Изначально это было важно при работе с тяжёлыми объектами, подключениями, кэшами или при реализации сложных паттернов (например, Singleton).
Проблема — если создавать все ресурсы сразу, то можно потратить лишнюю память, время и мощности даже тогда, когда объект не нужен. Ленивая инициализация решает задачу «отложенного» создания.
Решение обычно реализуется проверкой: если объект ещё не создан, мы его создаём, иначе возвращаем существующий.
Пример кода:
public class LazyHolder { private Resource resource; public Resource getResource() { if (resource == null) { resource = new Resource(); } return resource; } }
В многопоточных условиях необходим о синхронизации.
Ключевые особенности:
Является ли Double-Checked Locking надёжной реализацией для ленивой инициализации Singleton?
Только начиная с Java 5, т.к. раньше memory model этого не гарантировала. Необходимо использовать ключевое слово volatile для поля-ресурса.
private volatile Resource resource; public Resource getResource() { if (resource == null) { synchronized(this) { if (resource == null) { resource = new Resource(); } } } return resource; }
Имеет ли смысл делать ленивую инициализацию для лёгких объектов?
Как правило, нет. Лучше создавать "лёгкие" объекты сразу, чтобы не сокращать читаемость кода и не усложнять его лишней логикой.
Работает ли ленивая инициализация при сериализации объектов?
Не всегда. При сериализации может быть восстановлено "старое" состояние, либо потребуется дополнительная логика в readObject().
В high-load сервисе специальный пул объектов парсился лениво, но не был синхронизирован. Два потока одновременно инициализировали объект, приводя к утечкам памяти и непредсказуемым ошибкам.
Плюсы:
Минусы:
В крупном веб-приложении аналитика подключается только по API-вызову через ленивая инициализируемый прокси-объект с double-checked locking.
Плюсы:
Минусы: