Рассмотрите паттерн "double-checked locking" для ленивой инициализации в Java:\n private static MyObj instance;\n public static MyObj get(){\n if(instance==null){\n synchronized(MyClass.class){\n if(instance==null) instance=new MyObj();\n }\n }\n return instance;\n }\nОбъясните, почему такой код был некорректен в старых версиях Java, какие проблемы с видимостью и reorder возможны и как правильно реализовать ленивую потокобезопасную инициализацию

17 Мар в 08:37
16 +2
0
Ответы
1
Кратко и по существу.
Почему в старых версиях Java (до Java 555) DCL был некорректен
- Проблема — модель памяти (JMM) до JSR‑133 позволяла перестановки и не гарантировала видимость записей между потоками. Создание объекта фактически может выполняться в трёх логических шагах: 111 — выделить память, 222 — инициализировать объект, 333 — записать ссылку в поле. Без специальных гарантий шаги могут быть переупорядочены (например, 1,3,21,3,21,3,2). В результате другой поток может прочитать ненулевую ссылку и получить ссылку на ещё не полностью инициализированный объект (поля — дефолтные значения) — баги, в том числе приводящие к некорректному поведению или NPE.
- Также без volatile запись одного потока может не стать видимой другому вовремя (кэширование и отсутствие happens‑before).
Как правильно реализовать потокобезопасную ленивую инициализацию
1) Простое исправление DCL (корректно с Java 555 и выше):
- Объявить поле volatile:
private static volatile MyObj instance;
public static MyObj get() {
MyObj result = instance;
if (result == null) {
synchronized (MyClass.class) {
if (instance == null) instance = new MyObj();
result = instance;
}
}
return result;
}
- volatile предотвращает переупорядочивание и обеспечивает видимость; локальная переменная сокращает число обращений к volatile.
2) Надёжная и простая альтернатива — инициализатор на требование (holder idiom):
private static class Holder { static final MyObj INSTANCE = new MyObj(); }
public static MyObj get() { return Holder.INSTANCE; }
- Класс Holder инициализируется JVM лениво и потокобезопасно (гарантии загрузчика классов).
3) Другие варианты:
- synchronized метод: public static synchronized MyObj get() { if (instance==null) instance=new MyObj(); return instance; } — проще, но синхронизация на каждом вызове.
- enum‑singleton или фабрики — для одиночек.
Итого: до Java 555 DCL был небезопасен из‑за reorder/видимости; начиная с Java 555 корректный DCL требует volatile либо используйте holder idiom/enum/synchronized.
17 Мар в 08:43
Не можешь разобраться в этой теме?
Обратись за помощью к экспертам
Гарантированные бесплатные доработки в течение 1 года
Быстрое выполнение от 2 часов
Проверка работы на плагиат
Поможем написать учебную работу
Прямой эфир