Единый магазин для создания одноэлементных паттернов на Java

Синглтон - один из важнейших паттернов проектирования. Определение шаблона singleton согласно wiki:

В программной инженерии одноэлементный шаблон - это шаблон проектирования программного обеспечения, который ограничивает создание экземпляра класса одним «единственным» экземпляром. Это полезно, когда нужен ровно один объект для координации действий в системе.

Это один из наиболее часто задаваемых вопросов на собеседовании, будь то собеседование с интерфейсом или сервером. Если вы разработчик Android и ищете синглтон в kotlin, вот отличная статья:



Здесь мы поговорим об одноэлементном классе исключительно на Java и без сокращений. Мы можем писать одноэлементные классы в разных стилях. мы рассмотрим их плюсы и минусы один за другим.

Статическая инициализация:

Когда мы хотим создать экземпляр одноэлементного объекта еще до того, как класс вызывается в первый раз, мы используем этот тип инициализации.

public class StaticInitializedSingleton {

    private static final StaticInitializedSingleton instance = new StaticInitializedSingleton();

    //private constructor to avoid client applications to use constructor
    private StaticInitializedSingleton(){}

    public static StaticInitializedSingleton getInstance(){
        return instance;
    }
}

Объект инициализируется во время создания объекта. Это означает, что даже если это не требуется. Итак, у вас есть много одноэлементных классов, использующих много ресурсов, тогда это будет занимать много памяти.

Ленивая инициализация:

public class LazyInitializedSingleton {

    private static LazyInitializedSingleton instance;

    private LazyInitializedSingleton() {
    }

    public static LazyInitializedSingleton getInstance() {
        if (instance == null) {
            instance = new LazyInitializedSingleton();
        }
        return instance;
    }
}

Он создает экземпляр объекта, когда мы используем его в первый раз.

Это может вызвать проблемы, если несколько потоков одновременно пытаются получить доступ к условию if. Оба потока получат разные экземпляры класса singleton, которые нам не нужны.

Поточно-ориентированный синглтон:

Чтобы сделать блок метода потокобезопасным, мы будем использовать ключевое слово syncronized.

public class ThreadSafeSingleton {

    private static ThreadSafeSingleton instance;

    private ThreadSafeSingleton(){}

    public static synchronized ThreadSafeSingleton getInstance(){
        if(instance == null){
            instance = new ThreadSafeSingleton();
        }
        return instance;
    }

}

Эта реализация обеспечивает безопасность потоков, но добавляет дополнительные накладные расходы. Позволь мне объяснить. Если пять потоков пытаются получить доступ к этому методу, то четыре из них будут заблокированы, а один из них будет обращаться к блоку метода. В дополнение к этому мы без необходимости блокируем другие потоки, поскольку экземпляр уже создан. Давайте оптимизируем этот сценарий на следующем этапе.

Потокобезопасный-2 Синглтон:

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

public static ThreadSafeSingleton getInstanceUsingDoubleLocking(){
    if(instance == null){
        synchronized (ThreadSafeSingleton.class) {
            if(instance == null){
                instance = new ThreadSafeSingleton();
            }
        }
    }
    return instance;
}

Здесь мы обработали блокировку потоков, когда какой-либо поток пытается получить к нему доступ, когда экземпляр уже создан. Но что, если пять потоков пытаются получить к нему доступ впервые. Мы снова блокируем четыре из них, потому что для них instance впервые имеет значение null.

Есть ли способ решить все угловые случаи?

Реализация синглтона Билла Пью:

public class BillPughSingleton {

    private BillPughSingleton(){}

    private static class SingletonHelper{
        private static final BillPughSingleton INSTANCE = new BillPughSingleton();
    }

    public static BillPughSingleton getInstance(){
        return SingletonHelper.INSTANCE;
    }
}

У нас есть частный внутренний статический класс, который содержит экземпляр одноэлементного класса. Когда загружается одноэлементный класс, SingletonHelper класс не загружается в память, и только когда кто-то вызывает метод getInstance(), этот класс загружается и создает экземпляр класса Singleton. Вот отличное чтение, объясняющее, почему это потокобезопасно.

Билл Пью Синглтон - лучший подход на данный момент. Это была одна из основных реализаций синглтона. Но его легко разрушить с помощью Java-рефлексии. (это для другой статьи)

Enum Singleton:

Чтобы преодолеть эту ситуацию с Reflection, Джошуа Блох предлагает использовать Enum для реализации шаблона проектирования Singleton, поскольку Java гарантирует, что любое значение enum создается только один раз в программе Java.

public enum EnumSingleton {

    INSTANCE;

    public static void doSomething(){
        //do something
    }
}

Поскольку значения Java Enum доступны во всем мире, синглтон тоже. Недостатком является то, что тип enum несколько негибкий; например, он не допускает ленивую инициализацию.

Заключение:

Мы обсудили различные подходы к созданию класса Singleton. вы можете выбрать, какой из них выбрать, исходя из ваших требований и ограничений. Я вообще предпочитаю подход theBill Pugh Singleton Implementation.

Если вам понравилась эта статья, то покажите свою любовь, хлопнув в ладоши.