Ответ
Классическая «ленивая» (отложенная) реализация Singleton небезопасна для многопоточной среды. Если несколько потоков одновременно вызовут метод получения экземпляра, когда он ещё не создан, может быть инстанцировано несколько объектов, что нарушает сам паттерн.
Проблемная реализация:
public class UnsafeSingleton {
private static UnsafeSingleton instance;
private UnsafeSingleton() {}
public static UnsafeSingleton getInstance() {
if (instance == null) { // Поток А видит null
// Поток Б может тоже войти здесь, пока Поток А ещё не создал объект
instance = new UnsafeSingleton(); // Создаётся два разных объекта
}
return instance;
}
}
Потокобезопасные решения:
-
Eager Initialization (С нетерпением): Экземпляр создаётся при загрузке класса JVM.
public class EagerSingleton { private static final EagerSingleton INSTANCE = new EagerSingleton(); private EagerSingleton() {} public static EagerSingleton getInstance() { return INSTANCE; // Потокобезопасно за счёт ClassLoader } } -
Double-Checked Locking (Блокировка с двойной проверкой): Для ленивой инициализации с минимальными накладными расходами на синхронизацию.
public class ThreadSafeSingleton { // Ключевое слово volatile обязательно для корректной работы в Java 5+ private static volatile ThreadSafeSingleton instance; private ThreadSafeSingleton() {} public static ThreadSafeSingleton getInstance() { if (instance == null) { // Первая проверка (без блокировки) synchronized (ThreadSafeSingleton.class) { if (instance == null) { // Вторая проверка (под блокировкой) instance = new ThreadSafeSingleton(); } } } return instance; } } -
Holder Idiom (Статический внутренний класс): Элегантная ленивая загрузка без явной синхронизации.
public class HolderSingleton { private HolderSingleton() {} private static class SingletonHolder { private static final HolderSingleton INSTANCE = new HolderSingleton(); } public static HolderSingleton getInstance() { return SingletonHolder.INSTANCE; // INSTANCE создаётся при первом обращении к классу Holder } } -
Enum Singleton (Рекомендовано Джошуа Блохом): Наиболее простой и надёжный способ, потокобезопасный от природы и устойчивый к сериализации/рефлексии.
public enum EnumSingleton { INSTANCE; public void doBusinessLogic() { /* ... */ } } // Использование: EnumSingleton.INSTANCE.doBusinessLogic();
Выбор решения: Для большинства случаев Holder Idiom или Enum являются оптимальными.