Какие способы создания потокобезопасного Singleton в 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, если не требуется наследование или сложная ленивая инициализация.

Ответ 18+ 🔞

А, ну ты смотри, какие у нас тут синглтоны пошли, блядь! Как будто без них мир рухнет, ёпта. Слушай, я тебе как старый ворчун, который уже на этих паттернах собаку съел, расскажу, что к чему, а то народ до сих пор synchronized везде пихает, как будто это панацея, блядь.

Вот смотри, есть два адекватных способа, которые не заставят тебя потом ночами дебажить, почему в продакшене всё ебнулось.

Первый — это через enum, Джошуа Блох его в своём "Effective Java" вывел на пьедестал, как икону.

public enum Singleton {
    INSTANCE;

    public void doSomething() {
        // делай тут что хочешь
    }
}

Что тут хорошего, спросишь? А то, что JVM сама, сука, гарантирует, что этот INSTANCE будет один, и создаст его потокобезопасно. И никакой рефлексии, никакой сериализации его не сломают — железобетонно, блядь. Минус один, но похуй: ленивой инициализации в чистом виде нет, enum загрузится при первом обращении к классу. Но это обычно не проблема, ебать мои старые костыли.

Второй — через статического холдера, хитрая жопа, но умная.

public class Singleton {
    private Singleton() {}

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

    public static Singleton getInstance() {
        return Holder.INSTANCE;
    }
}

Вот это уже чистая ленивость, ёпта! Экземпляр создастся только когда ты впервые вызовешь getInstance(), потому что класс Holder загрузится в этот момент. И опять же, потокобезопасность на отлично — JVM рулит загрузкой классов. Но! Если какой-нибудь пидарас шерстяной возьмёт рефлексию и вызовет твой приватный конструктор — всё, пиздец, можно второй экземпляр создать. Так что если у тебя окружение недоверенное — лучше enum.

А теперь, блядь, почему вся эта классика — говно. Ну смотри:

  • synchronized на методе getInstance() — это же овердохуища накладных расходов! Каждый вызов синхронизация, хотя она нужна только при самом первом создании. После инициализации она просто тормозит всё, как чугунный мост.
  • Double-Checked Locking — о, это отдельная песня, блядь! До Java 5 он вообще не работал как надо из-за модели памяти, а сейчас, чтобы его правильно написать, надо volatile и прочую хуйню помнить. Зачем так жить, а? Зачем?

Короче, вывод простой, как палка: если тебе не надо наследоваться и выёбываться — бери enum и живи спокойно. Он тебя от большинства граблей спасёт. А если прям очень хочется ленивую инициализацию и ты уверен, что рефлексии не будет — холдер в помощь. Всё остальное — это путь в ад, чувак.