Дан Java-фрагмент, демонстрирующий паттерн Singleton: public class Singleton { private static Singleton instance; private Singleton(){} public static Singleton getInstance(){ if (instance == null) instance = new Singleton(); return instance; } } — объясните проблему этого варианта в многопоточной среде и предложите как минимум два безопасных варианта реализации Singleton с объяснением плюсов и минусов

8 Окт в 11:36
4 +2
0
Ответы
1

Проблема исходного варианта в многопоточной среде

Ваша реализация не потокобезопасна. Если два потока одновременно вызовут getInstance и увидят instance == null, оба могут создать по одному объекту — нарушается инвариант Singleton.Кроме того, без дополнительных гарантий возможны тонкие проблемы с видимостью кэш−память,реорганизацияинструкцийкэш-память, реорганизация инструкцийкэшпамять,реорганизацияинструкций, особенно при частичной инициализации объекта в слабых моделях памяти встарыхреализацияхJVMв старых реализациях JVMвстарыхреализацияхJVM.

Ниже — несколько безопасных вариантов с разбором плюсов/минусов.

1) Простая синхронизация метода
Код:
public class Singleton {
private static Singleton instance;
private Singleton {}
public static synchronized Singleton getInstance {
if instance==nullinstance == nullinstance==null instance = new Singleton;
return instance;
}
}
Плюсы:

Очень просто, корректно в любых версиях Java.Гарантирует единственность и видимость.
Минусы:Синхронизация на каждый вызов getInstance — потенциальная потеря производительности узкоеместоузкое местоузкоеместо при частых обращениях.

2) Double-checked locking сvolatileс volatileсvolatile — эффективный ленивый вариант
Код требуетJava5+требует Java 5+требуетJava5+:
public class Singleton {
private static volatile Singleton instance;
private Singleton {}
public static Singleton getInstance {
if instance==nullinstance == nullinstance==null {
synchronized Singleton.classSingleton.classSingleton.class {
if instance==nullinstance == nullinstance==null instance = new Singleton;
}
}
return instance;
}
}
Плюсы:

Ленивое создание создаетсяприпервомобращениисоздается при первом обращениисоздаетсяприпервомобращении.Накладные расходы на синхронизацию только при первой инициализации; дальше — быстрые чтения.
Минусы:Более сложен, требует volatile; до Java 5 этот паттерн был некорректен из‑за реорганизации инструкций.Сложнее для понимания/поддержки.

3) Initialization-on-demand holder рекомендуемыйленивыйподходрекомендуемый ленивый подходрекомендуемыйленивыйподход Код:
public class Singleton {
private Singleton {}
private static class Holder {
static final Singleton INSTANCE = new Singleton;
}
public static Singleton getInstance {
return Holder.INSTANCE;
}
}
Плюсы:

Ленивое создание, безопасно в многопоточности инициализациястатическоговложенногоклассавыполняетсяJVMсинхронноинициализация статического вложенного класса выполняется JVM синхронноинициализациястатическоговложенногоклассавыполняетсяJVMсинхронно.Нет явной синхронизации и накладных расходов на доступ.Прост и надежен.
Минусы:Немного менее очевидный приём для новичков ноширокоиспользуемибезопасенно широко используем и безопасенноширокоиспользуемибезопасен.

4) Eager initialization жаднаяинициализацияжадная инициализацияжаднаяинициализация Код:
public class Singleton {
private static final Singleton INSTANCE = new Singleton;
private Singleton {}
public static Singleton getInstance {
return INSTANCE;
}
}
Плюсы:

Очень прост, потокобезопасен статическаяинициализацияJVMстатическая инициализация JVMстатическаяинициализацияJVM.Никакой синхронизации в рантайме.
Минусы:Объект создается при загрузке класса, даже если он никогда не будет использован неленивыйне ленивыйнеленивый.

5) Enum рекомендуется,еслиподходитсемантикерекомендуется, если подходит семантикерекомендуется,еслиподходитсемантике Код:
public enum Singleton {
INSTANCE;
// поля и методы
}
Плюсы:

Самый простой и безопасный способ: корректен в многопоточности, защищает от сериализации несоздастновыйэкз.придесериализациине создаст новый экз. при десериализациинесоздастновыйэкз.придесериализации и устойчив к большинству рефлексивных атак.Является рекомендуемым паттерном для singleton в Effective Java JoshuaBlochJoshua BlochJoshuaBloch.
Минусы:Не подходит, если Singleton должен наследоваться от какого‑то другого класса enumуженаследуетjava.lang.Enumenum уже наследует java.lang.Enumenumуженаследуетjava.lang.Enum.Некоторым разработчикам стиль enum может показаться непривычным для «обычного» объекта.

Дополнительные замечания

Сериализация: если вы используете обычный класс, учтите, что при десериализации может создаваться новый экземпляр; можно реализовать readResolve, чтобы вернуть единственный экземпляр.Рефлексия: через Constructor#setAccessibletruetruetrue можно попытаться создать ещё один экземпляр; enum защищает от этого лучше. Для обычного класса можно в конструкторе проверять, не был ли уже создан экземпляр, и бросать исключение.Выбор: если нужна ленивость и максимальная простота — Initialization-on-demand holder; если важна защита от сериализации/рефлексии — enum; если система старше Java 5 — используйте синхронизированный метод илистатическуюинициализациюили статическую инициализациюилистатическуюинициализацию.

Если нужно, могу показать пример защиты от сериализации/рефлексии для конкретной реализации или провести сравнение производительности в вашем окружении.

8 Окт в 12:17
Не можешь разобраться в этой теме?
Обратись за помощью к экспертам
Гарантированные бесплатные доработки в течение 1 года
Быстрое выполнение от 2 часов
Проверка работы на плагиат
Поможем написать учебную работу
Прямой эфир