Какие способы создания потокобезопасного Singleton в Java?

«Какие способы создания потокобезопасного Singleton в Java?» — вопрос из категории Паттерны, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

В Java существует несколько способов реализации паттерна Singleton. Наиболее надежные и рекомендуемые — с использованием enum или статического внутреннего класса (Initialization-on-demand holder idiom).

1. Enum Singleton (рекомендуемый по Effective Java):

public enum Singleton {
    INSTANCE;

    public void doSomething() {
        // реализация метода
    }
}
  • Плюсы: автоматическая потокобезопасность, защита от рефлексии и сериализации, лаконичный синтаксис.
  • Минусы: не поддерживает ленивую инициализацию в классическом понимании (enum загружается при первом обращении).

2. Singleton через статический внутренний класс (Holder):

public class Singleton {
    private Singleton() {}

    private static class Holder {
        static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return Holder.INSTANCE;
    }
}
  • Плюсы: ленивая инициализация (экземпляр создается при первом вызове getInstance()), потокобезопасность за счет механизмов загрузки классов JVM.
  • Минусы: уязвим к рефлексии (можно обойти приватный конструктор).

Почему эти способы лучше классических?

  • Классический synchronized метод (getInstance()) создает избыточную синхронизацию после инициализации, снижая производительность.
  • Double-Checked Locking сложен в корректной реализации (до Java 5 был сломан из-за проблем с моделью памяти).

Вывод: для большинства случаев используйте enum, если не требуется наследование или сложная ленивая инициализация.