Разберите ленивую инициализацию singleton с double-checked locking: зачем ссылке на экземпляр нужен volatile?
volatile нужен, чтобы публикация ссылки на singleton имела гарантии видимости и порядка. Запись ссылки в volatile-поле происходит после конструктора, а чтение этой ссылки другим потоком не может наблюдать ссылку на объект до завершения действий, предшествующих volatile-записи. Без volatile double-checked locking некорректен: поток может увидеть ненулевую ссылку, но некорректно инициализированное состояние объекта.
Ленивая инициализация singleton создаёт объект только при первом обращении, экономя ресурсы и время запуска. Наивная синхронизация всего метода обеспечивает корректность, но добавляет блокировку каждому последующему вызову.
Double-checked locking появился как попытка синхронизировать только редкий путь создания объекта. Однако до формализации современных гарантий Java Memory Model такой шаблон считался небезопасным: модель памяти не давала нужных гарантий видимости и порядка публикации.
Создание объекта логически включает выделение памяти, выполнение конструктора и присваивание ссылки. Эти действия могут быть переупорядочены относительно наблюдения другим потоком, если между публикацией и чтением нет подходящей границы happens-before.
Без volatile второй поток способен увидеть ненулевую ссылку и пропустить внутреннюю синхронизацию. В результате он может обратиться к полям объекта до того, как увидит их корректные значения. Это приводит к трудно воспроизводимым ошибкам, зависящим от оптимизаций JVM и аппаратной архитектуры.
volatile для ссылки обеспечивает две ключевые гарантии:
Внутренняя проверка под synchronized предотвращает создание нескольких экземпляров. Внешняя проверка сокращает накладные расходы после инициализации: большинство обращений читает уже опубликованную volatile-ссылку и не входит в монитор.
Минимальная реализация выглядит так:
Локальная переменная value здесь не заменяет volatile: чтение из неё лишь уменьшает число обращений к полю после первичного volatile-чтения. Поле всё равно должно быть volatile.
Практически чаще выбирают инициализацию при загрузке класса или holder-идиому: они проще и используют гарантии инициализации класса. Double-checked locking оправдан, когда нужна именно ленивая инициализация, а альтернативный механизм создания объекта неудобен.
Сервис содержит тяжёлый клиент внешней системы, который нужен только части запросов. Полная синхронизация метода создаёт singleton корректно, но добавляет вход в монитор на каждый запрос. Реализация double-checked locking без volatile иногда возвращает клиент с неготовыми внутренними структурами, поэтому такой вариант нельзя принимать.
Возможны три решения. Синхронизация всего метода проще, но дороже на горячем пути. Holder-идиома обычно надёжнее и проще, однако не подходит, если требуется явно управлять моментом и условиями создания. Double-checked locking с volatile сохраняет ленивость и быстрый путь чтения, поэтому его выбирают при обоснованной необходимости; результатом становится корректная публикация без блокировки уже после создания объекта.
Нет. Volatile внутреннего поля может обеспечить отдельную видимость операций с этим полем, но не делает безопасной публикацию самой ссылки и не гарантирует корректную видимость всех остальных полей объекта. Для double-checked locking volatile должна быть именно ссылка, публикуемая между потоками.
Если все чтения и записи ссылки выполняются под одним и тем же монитором, отдельный volatile для корректности публикации не обязателен: разблокировка монитора happens-before последующей блокировки того же монитора. Но double-checked locking специально читает ссылку вне монитора, поэтому внешнее чтение требует volatile или другого эквивалентного механизма.
final-поля имеют специальные гарантии видимости после корректного завершения конструктора, но они не заменяют безопасную публикацию всей ссылки. Обычные поля, массивы, ссылки на изменяемые структуры и последующие изменения могут оставаться невидимыми или наблюдаться некорректно. Поэтому для этого шаблона всё равно требуется volatile-ссылка либо другой механизм безопасной публикации.