← Назад к вопросам

Как создать Singleton в Java?

2.0 Middle🔥 151 комментариев
#Другое

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как создать Singleton в Java

Singleton — это паттерн проектирования, который гарантирует, что класс имеет только один экземпляр, и предоставляет глобальную точку доступа к этому экземпляру. Это один из самых популярных и часто используемых паттернов в Java.

Когда использовать Singleton

  • Логирование (Logger)
  • Конфигурация приложения
  • Подключение к БД
  • Thread Pool
  • Кэширование
  • История (History) изменений

Способ 1: Eager Initialization (Ленивая инициализация)

Объект создаётся при загрузке класса:

public class Singleton {
    private static final Singleton INSTANCE = new Singleton();
    
    // Приватный конструктор, чтобы нельзя было создать новый объект
    private Singleton() {
    }
    
    public static Singleton getInstance() {
        return INSTANCE;
    }
}

Плюсы:

  • Простой и потокобезопасный код
  • Нет синхронизации (производительность)

Минусы:

  • Объект создаётся всегда, даже если не используется
  • Если создание дорогое, памяти тратится впустую

Способ 2: Lazy Initialization с синхронизацией

Объект создаётся при первом обращении:

public class Singleton {
    private static Singleton instance;
    
    private Singleton() {
    }
    
    public synchronized static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

Плюсы:

  • Объект создаётся только если нужен
  • Потокобезопасно

Минусы:

  • Синхронизация замораживает всё приложение
  • Даже после создания объекта каждый вызов getInstance() требует синхронизации

Способ 3: Double-Checked Locking (Двойная проверка)

Комбинирует ленивую инициализацию с минимальной синхронизацией:

public class Singleton {
    private static volatile Singleton instance;
    
    private Singleton() {
    }
    
    public static Singleton getInstance() {
        if (instance == null) {  // Первая проверка без блокировки
            synchronized (Singleton.class) {
                if (instance == null) {  // Вторая проверка в блокировке
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

Плюсы:

  • Ленивая инициализация
  • Синхронизация только при создании объекта
  • Потом нет оверхеда синхронизации

Минусы:

  • Сложнее для понимания
  • Нужно помнить про volatile

ВАЖНО: volatile гарантирует, что изменения в instance видны всем потокам благодаря happens-before relationship.

Способ 4: Initialization on Demand Holder Class (ЛУЧШИЙ)

Использует особенности ClassLoader для потокобезопасности:

public class Singleton {
    
    private Singleton() {
    }
    
    // Внутренний класс, который загружается только при обращении
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
    
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

Плюсы:

  • Ленивая инициализация
  • Потокобезопасно без synchronized
  • Производительность максимальная
  • Простой и элегантный код
  • JVM гарантирует, что SingletonHolder.INSTANCE инициализируется только один раз

Минусы:

  • Нужно понимать, как работает ClassLoader

ЭТО РЕКОМЕНДУЕМЫЙ СПОСОБ!

Способ 5: Enum (ОЧЕНЬ ХОРОШИЙ)

Использует enum как Singleton:

public enum Singleton {
    INSTANCE;
    
    public void doSomething() {
        System.out.println("Singleton method called");
    }
}

// Использование
public class Client {
    public static void main(String[] args) {
        Singleton singleton = Singleton.INSTANCE;
        singleton.doSomething();
    }
}

Плюсы:

  • Очень потокобезопасен (гарантия JVM)
  • Защищён от reflection и десериализации
  • Самый простой и понятный синтаксис
  • Enum нельзя скопировать через reflection

Минусы:

  • Нужно наследовать Enum (нельзя наследовать от другого класса)
  • Может быть странно выглядеть для Singleton

Рекомендуется для большинства случаев!

Защита от рефлексии и десериализации

Если нужна защита от попыток создать несколько экземпляров:

public class SecureSingleton {
    private static volatile SecureSingleton instance;
    private static boolean instantiated = false;
    
    private SecureSingleton() {
        if (instantiated) {
            throw new IllegalStateException("Already instantiated!");
        }
        instantiated = true;
    }
    
    public static SecureSingleton getInstance() {
        if (instance == null) {
            synchronized (SecureSingleton.class) {
                if (instance == null) {
                    instance = new SecureSingleton();
                }
            }
        }
        return instance;
    }
    
    // Защита от десериализации
    protected Object readResolve() {
        return getInstance();
    }
}

Сравнение способов

СпособЛениваяПотокобезопасныйПроизводительностьСложность
EagerНетДаОтличнаяНизкая
SynchronizedДаДаПлохаяНизкая
Double-CheckedДаДаХорошаяСредняя
HolderДаДаОтличнаяСредняя
EnumДаДаОтличнаяНизкая

Тестирование Singleton

public class SingletonTest {
    
    @Test
    public void testSingletonInstance() {
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        
        assertSame(instance1, instance2); // Это должно быть true
    }
    
    @Test
    public void testMultiThreadedSingleton() throws InterruptedException {
        Set<Singleton> instances = Collections.synchronizedSet(new HashSet<>());
        
        Runnable runnable = () -> instances.add(Singleton.getInstance());
        
        Thread[] threads = new Thread[10];
        for (int i = 0; i < 10; i++) {
            threads[i] = new Thread(runnable);
        }
        
        for (Thread thread : threads) {
            thread.start();
        }
        
        for (Thread thread : threads) {
            thread.join();
        }
        
        assertEquals(1, instances.size()); // Только один объект
    }
}

Когда НЕ использовать Singleton

  • Если нужна гибкость конфигурации
  • Если нужно несколько экземпляров разных типов
  • Если нужна инъекция зависимостей (лучше использовать Spring)
  • Если нужна замена реализации для тестов

Spring и Singleton

В Spring все бины по умолчанию Singleton:

@Component
public class MyService {
    // Один экземпляр на всё приложение
}

Вывод

  • Для большинства случаев: используй enum или Holder class
  • Для максимальной гибкости: используй dependency injection (Spring)
  • Помни о потокобезопасности: особенно в многопоточных приложениях
  • Тестируй: убедись, что создаётся только один экземпляр
Как создать Singleton в Java? | PrepBro