Elettracompany.com

Компьютерный справочник
1 просмотров
Рейтинг статьи
1 звезда2 звезды3 звезды4 звезды5 звезд
Загрузка...

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

Синглтон в java

5 Ishi [2010-07-17 21:45:00]

Мне просто нужно прочитать следующий код:

Мне нужно знать, в чем состоит эта часть:

Что делать, если мы не используем эту часть кода? Там все равно будет одна копия SingletonObjectDemo , зачем нам нужен этот код?

java static singleton lazy-loading

7 ответов

5 Решение samitgaur [2010-07-17 22:02:00]

Этот класс имеет поле SingletonObjectDemo singletonObject , которое содержит экземпляр singleton. Теперь есть две возможные стратегии —

1 — Вы пытаетесь инициализировать объект с объявлением —

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

2 — Вы выполняете ленивую инициализацию объекта, то есть инициализируете его при первом вызове getSingletonObject() —

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

По ленивой и нетерпеливой инициализации

Оператор if представляет собой реализацию метода ленивой инициализации.

Более явная версия выглядит следующим образом:

Случается, что в первый раз gimmeStuff() вызывается firstTime будет true , поэтому stuff будет инициализироваться на new Stuff() . При последующих вызовах firstTime будет false , поэтому new Stuff() больше не будет вызываться.

Таким образом, stuff инициализируется «лениво». Он не был инициализирован до самого первого раза, когда это было необходимо.

См. также

Безопасность потока

Нужно сказать, что фрагмент не является потокобезопасным. Если существует несколько потоков, то в некоторых условиях гонки new SingletonObjectDemo() может вызываться несколько раз.

Одним из решений является метод synchronized getSingletonObject() . Тем не менее, для ВСЕХ вызовов на getSingletonObject() есть дополнительные затраты на синхронизацию. Так называемая дважды проверенная блокировка идиома затем используется, чтобы попытаться исправить это, но в Java эта идиома фактически не работает до тех пор, пока J2SE 5.0 с введением ключевого слова volatile в новую модель памяти.

Излишне говорить, что правильное применение одноэлементного шаблона — это не тривиальная вещь.

См. также

Связанные вопросы

Эффективное Java 2nd Edition

Вот что должна сказать книга по этим темам:

Пункт 71: разумно используйте ленивую инициализацию

Как и в случае большинства оптимизаций, лучшим советом для ленивой инициализации является «не делайте этого, если вам не нужно». Ленивая инициализация — обоюдоострый меч. Это уменьшает стоимость инициализации класса или создания экземпляра за счет увеличения стоимости доступа к лениво инициализированному полю. В зависимости от того, какая часть лениво инициализированных полей в конечном итоге требует инициализации, насколько дорого их инициализировать, и как часто обращаются к каждому полю, ленивая инициализация (как и многие «оптимизации» на самом деле вредит производительности).

При наличии нескольких потоков ленивая инициализация сложна. Если два или более потока разделяют лениво инициализированное поле, важно, чтобы использовалась некоторая форма синхронизации, или могут возникнуть серьезные ошибки.

В большинстве случаев нормальная инициализация предпочтительнее ленивой инициализации.

Пункт 3: Принудительное использование свойства singleton с помощью частного конструктора или типа enum

Начиная с версии 1.5. существует третий подход к реализации синглетонов. Просто введите тип перечисления с одним элементом. [. ] Этот подход функционально эквивалентен полевому подходу public , за исключением того, что он более краткий, обеспечивает бесплатный механизм сериализации и обеспечивает надежную гарантию против множественного экземпляра даже в условиях сложной сериализации или основанной на отражениях атаки.

[. ] Одноэлементный тип перечисления — лучший способ реализовать синглтон.

Связанные вопросы

В режиме enum singleton/Java:

По достоинствам и альтернативам одноэлементного шаблона:

Что делать, если мы не используем эту часть кода? Будет ли еще одна копия SingletonObjectDemo, зачем нам этот код?

Идея состоит в том, чтобы ленить загрузку синглтона, то есть загружать экземпляр только при необходимости. Почему вы хотите это сделать? Ну, Боб Ли очень хорошо описывает это в Lazy Loading Singletons:

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

Но реализация, которую вы показываете, сломана, она не является потокобезопасной, а две параллельные потоки могут фактически создавать два экземпляра. лучший способ сделать ваш ленивый загруженный однопользовательский поток безопасным будет использовать Инициализация по требованию (IODH) idiom, который очень прост и имеет ноль. Цитирование эффективной Java, пункт 71: разумно используйте ленивую инициализацию (акцент не мой):

Если вам нужно использовать ленивую инициализацию для производительности на статическое поле, используйте ленивый идентификатор класса инициализации. Эта идиома (также известная как Инициация класса инициализации по требованию) использует гарантию того, что класс не будет инициализирован до тех пор, пока он используется [JLS, 12.4.1]. Вот как это выглядит:

Когда вызывается метод getField в первый раз, он читает FieldHolder.field для первого время, вызвав класс FieldHolder для инициализации. Красота этого идиома заключается в том, что метод getField не синхронизированы и выполняют только доступ к полю, такая ленивая инициализация практически ничего не добавляет к стоимости доступа. Современная ВМ будет синхронизировать доступ к полю только для инициализировать класс. Как только класс инициализируется, VM будет кода, чтобы последующий доступ к поле не требует какого-либо тестирования или синхронизации.

См. также

  • Пункт 66: Синхронизация доступа к совместно используемым изменяемым данным — Эффективное Java 2nd edition
  • Пункт 71: разумно используйте ленивую инициализацию — Эффективное Java 2nd edition
  • Исправлена ​​ли новая модель памяти проблемой «двойной проверки» ?

3 rsp [2010-07-17 22:18:00]

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

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

Btw, чтобы вернуться к вашему вопросу, строка:

объявляет статическую ссылку, но она не будет автоматически распределять экземпляр, эта ссылка устанавливается на null компилятором Java.

1 Donnie [2010-07-17 21:48:00]

Приведенный класс не создает первый экземпляр объекта до его запроса. Поле private static равно null до первого запроса, тогда экземпляр объекта строится и сохраняется там. Последующие запросы возвращают один и тот же объект.

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

0 Klark [2010-07-17 21:48:00]

Сначала singletonObject имеет значение null. Идея синглтона состоит в том, чтобы инициализировать этот объект в первый раз, когда кто-то вызывает getSingletonObject(). Если вы не вызываете конструктор в этой части, переменная всегда будет нулевой.

  • отвечает за создание первого объекта
  • предотвращает создание другого

Lazy Initialization, Singleton Pattern and Double Checked locking

Lazy Initialization

Lazy Initialization is a technique where one postpones the instantiation of a object until its first use. In other words the instance of a class is created when its required to be used for the first time. The idea behind this is to avoid unnecessary instance creation. But there are concerns related to using such approaches in a concurrent scenario. But before that lets see how lazy initialization looks:

Читать еще:  Как восстановить поврежденный файл ворд на флешке

also read:

When the above code is executed in a multi threaded program the concern that immediately comes to the mind is the “Race condition”.
Thread A would check that resource == null so it goes inside to create the instance but might go into Runnable status before it creates the Resource instance.
Thread B also checks that resource == null and it goes ahead, creates the instance and uses it for its operations.
Thread A comes back to running status and it also continues to create its own instance and start using it.

Now in the above case suppose we wanted both Thread A and Thread B to work on the same instance, but due to the race condition they would work on different instances. If the Resource instances has state information associated with it then it because a critical problem. Lets stop here and then see how Singleton is related to this.

Singleton

Singleton is one of the Creational Design Patterns discussed in the GoF Design Patterns. The main idea behind the Singleton pattern is to control the creation of objects for a given class such that at any time there’s only one instance of that particular class. So lets see how we can implement the Singleton pattern:

where in the above example Resource is a singleton. This is very similar to the concept of lazy initialization and this as well suffers from the issues seen while using this is a multi threaded environment/program.

Double Checked Locking

One way to solve the problem with Lazy initialization and singleton in a multi threaded program is to declare the getInstance() or getResource() method as synchronized. But its a huge overhead because everytime the method is invoked it has to go through the process of waiting for the lock and then obtaining the lock and its unnecessary because its only once that the instance is created and all subsequent invocations would just return that instance.

To work around this issue, Double Checked Locking is the pattern used. In this approach the methods are not synchronized instead the instance creation code is put in a synchronized block. Let me show an example:

In the above code there are two null checks- one outside the synchronized block and one within it. The synchronized block is executed at most once and any other thread trying to get instance would have to wait until the instantiation to complete.

The above approach also has some issues. The initialization of an object and the creation of instance of that class are 2 different operations. Once the instance of the class has been created that instance is then used to initialize the values of its state either with default values or some user defined values. Now between these 2 operations another thread might invoke the method to get the instance and sees that the instance is already not null and then proceeds with using that instance. Though its the correct instance we wanted the thread to use but that instance hasn’t been initialized yet. This issue is called as Unsafe Publication. Java Concurrency in Practice book has a really good chapter on Java Memory Model and issues related to Safe Publication and Unsafe Publication of instances.

Lazy Initialization holder class idiom

One can read about this Lazy Initialization holder class Idiom in the interview with Joshua Bloch here. It has been covered in Effective Java by Joshua Bloch and also in Java Concurrency in Practice. Let me just share that idea here:

This takes advantage of:

  • Eager Initialization
  • Class not initialized unless its used/loaded by the class loader the first time its used.

Another interesting discussion related to this on JavaRanch.

Примеры шаблонов проектирования Java Singleton с примерами

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

  1. Стремительная инициализация: это самый простой метод создания одноэлементного класса. При этом объект класса создается, когда он загружается в память JVM. Это делается путем непосредственного присвоения ссылки экземпляру.
    Его можно использовать, когда программа всегда будет использовать экземпляр этого класса, или если стоимость создания экземпляра не слишком велика с точки зрения ресурсов и времени.

// Java-код для создания одноэлементного класса
// Стремительная инициализация

public class GFG

// публичный экземпляр инициализируется при загрузке класса

private static final GFG instance = new GFG();

public static GFG getInstance()<

Плюсы:

  1. Очень прост в реализации.

Минусы:

  1. Может привести к потере ресурсов. Потому что экземпляр класса создается всегда, требуется ли он или нет.
  2. Время ЦП также теряется при создании экземпляра, если это не требуется.
  3. Обработка исключений невозможна.
  • Использование статического блока: это также часть инициализации Eager. Единственное отличие состоит в том, что объект создается в статическом блоке, поэтому мы можем иметь доступ к его созданию, например, к обработке исключений. Таким же образом объект создается во время загрузки класса.
    Его можно использовать, когда есть вероятность исключений при создании объекта с активной инициализацией.

    // Java-код для создания одноэлементного класса
    // Использование статического блока

    public class GFG

    public static GFG instance;

    // статический блок для инициализации экземпляра

    instance = new GFG();

    Плюсы:

    1. Очень прост в реализации.
    2. Нет необходимости реализовывать метод getInstance (). Экземпляр может быть доступен напрямую.
    3. Исключения могут быть обработаны в статическом блоке.

    Минусы:

    1. Может привести к потере ресурсов. Потому что экземпляр класса создается всегда, требуется ли он или нет.
    2. Время ЦП также теряется при создании экземпляра, если это не требуется.
  • Ленивая инициализация: в этом методе объект создается, только если он необходим. Это может предотвратить потерю ресурсов. Требуется реализация метода getInstance (), который возвращает экземпляр. Существует нулевая проверка, что если объект не создан, то создать, в противном случае вернуть ранее созданный. Чтобы убедиться, что класс не может быть создан каким-либо другим способом, конструктор делается финальным. Поскольку объект создается с помощью метода, он гарантирует, что объект не будет создан до тех пор, пока он не потребуется. Экземпляр остается закрытым, чтобы никто не мог получить к нему доступ напрямую.
    Его можно использовать в однопоточной среде, поскольку несколько потоков могут нарушать свойство singleton, поскольку они могут одновременно обращаться к методу get instance и создавать несколько объектов.

    // Java-код для создания одноэлементного класса
    // с ленивой инициализацией

    public class GFG

    // частный экземпляр, так что он может быть

    // доступ только к методу getInstance ()

    private static GFG instance;

    // метод для возврата экземпляра класса

    public static GFG getInstance()

    if (instance == null )

    // если экземпляр равен нулю, инициализировать

    instance = new GFG();

    Читать еще:  Проги для восстановления данных с жесткого диска

    Плюсы:

    1. Объект создается только если он нужен. Это может преодолеть ресурсное преодоление и потерю процессорного времени.
    2. Обработка исключений также возможна в методе.

    Минусы:

    1. Каждый раз, когда условие нуля должно быть проверено.
    2. экземпляр не может быть доступен напрямую.
    3. В многопоточной среде это может нарушить свойство singleton.
  • Потокобезопасный синглтон: создан потокобезопасный синглтон, так что свойство синглтона поддерживается даже в многопоточной среде. Чтобы сделать одноэлементный класс потокобезопасным, метод getInstance () синхронизирован, поэтому несколько потоков не могут получить к нему доступ одновременно.

    // Java-программа для создания Thread Safe
    // Синглтон класс

    public class GFG

    // частный экземпляр, так что он может быть

    // доступ только к методу getInstance ()

    private static GFG instance;

    // синхронизированный метод для контроля одновременного доступа

    synchronized public static GFG getInstance()

    if (instance == null )

    // если экземпляр равен нулю, инициализировать

    instance = new GFG();

    Плюсы:

    1. Ленивая инициализация возможна.
    2. Это также потокобезопасный.

    Минусы:

    1. Метод getInstance () синхронизирован, поэтому он снижает производительность, так как несколько потоков не могут получить к нему доступ одновременно.
  • Ленивая инициализация с двойной проверкой блокировки: в этом механизме мы преодолеваем проблему накладных расходов синхронизированного кода. В этом методе getInstance не синхронизируется, но блок, который создает экземпляр, синхронизируется, так что минимальное количество потоков должно ждать, и это только в первый раз.

    // Java-код для объяснения двойной проверки блокировки

    public class GFG

    // частный экземпляр, так что он может быть

    // доступ только к методу getInstance ()

    private static GFG instance;

    public static GFG getInstance()

    if (instance == null )

    // синхронизированный блок для удаления накладных расходов

    synchronized (GFG. class )

    if (instance== null )

    // если экземпляр равен нулю, инициализировать

    instance = new GFG();

    Плюсы:

    1. Ленивая инициализация возможна.
    2. Это также потокобезопасный.
    3. Из-за синхронизированного ключевого слова снижается производительность.

    Минусы:

    1. В первый раз это может повлиять на производительность.

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

    Реализация синглтона Билла Пью: до Java5 у модели памяти было много проблем, и вышеупомянутые методы вызывали сбои в определенных сценариях в многопоточной среде. Итак, Билл Пью предложил концепцию внутренних статических классов для использования в синглтоне.

    // Java-код для реализации Билла Пью Синглтона

    public class GFG

    // Внутренний класс для предоставления экземпляра класса

    private static class BillPughSingleton

    private static final GFG INSTANCE = new GFG();

    public static GFG getInstance()

    Когда класс синглтона загружен, внутренний класс не загружается и, следовательно, не создает объект при загрузке класса. Внутренний класс создается только при вызове метода getInstance (). Так что это может показаться нетерпеливой инициализацией, но это ленивая инициализация.
    Это наиболее широко используемый подход, поскольку он не использует синхронизацию.

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

    1. Стремительная инициализация проста в реализации, но может привести к потере ресурсов и времени процессора. Используйте его только в том случае, если стоимость инициализации класса меньше с точки зрения ресурсов или вашей программе всегда будет нужен экземпляр класса.
    2. Используя статический блок в инициализации Eager, мы можем обеспечить обработку исключений, а также можем контролировать экземпляр.
    3. Используя синхронизированный, мы можем создать синглтон-класс в многопоточной среде, но это может привести к снижению производительности, поэтому мы можем использовать механизм двойной проверки блокировки.
    4. Реализация Билла Пью является наиболее широко используемым подходом для одноэлементных классов. Большинство разработчиков предпочитают его из-за его простоты и преимуществ.

    Эта статья предоставлена Вишалом Гаргом . Если вы как GeeksforGeeks и хотели бы внести свой вклад, вы также можете написать статью с помощью contribute.geeksforgeeks.org или по почте статьи contribute@geeksforgeeks.org. Смотрите свою статью, появляющуюся на главной странице GeeksforGeeks, и помогите другим вундеркиндам.

    Пожалуйста, пишите комментарии, если вы обнаружите что-то неправильное или вы хотите поделиться дополнительной информацией по обсуждаемой выше теме.

    Java Потокобезопасная Ленивая Инициализация

    У меня есть веб-приложение. Выполняется на tomcat и нескольких потоках, обслуживающих вызовы Servlet .

    У меня есть класс User , класс Account и класс 1AccountContext.

    Accounts может иметь несколько Users .

    Только 1 экземпляр AccountContext должен храниться в памяти на Account .

    Когда пользователь делает входящий вызов через сервлет: если AccountContext существует, верните его. В противном случае, инициализируйте его.

    Ниже приведен код, который я написал для инициализации контекста. Выглядит ли этот код так, как он будет делайте то, что я хочу, будучи потокобезопасным?

    ACCOUNT_CONTEXT_MAP -это ConcurrentHashMap .

    3 Ответа

    Я бы использовал атомарный метод ConcurrentHashMap.putIfAbsent вместо синхронизации, который специально разработан для таких ситуаций. Вот как это используется:

    IMHO это не потокобезопасно , если вы не гарантируете, что все потоки имеют один и тот же экземпляр учетной записи , и нет никакого способа иметь два объекта учетной записи, представляющих один и тот же «account», рассмотрим следующий сценарий: два потока имеют объект учетной записи каждый, представляющий одну и ту же учетную запись, они оба вызывают getAccountContext(), первый поток приостанавливается сразу после строки if(accountContext == null) , но перед началом инициализации, затем второй поток попадает в этот же объект, проверяет, что accountContext является null и продолжает создавать AccountContext, затем первый поток дается снова CPU раз, так как этот первый поток уже имеет «verified», что accountContext является null, он будет продолжать создавать другой экземпляр.

    Попробуйте синхронизировать с помощью карты itselt (ACCOUNT_CONTEXT_MAP), а не каждый объект учетной записи.

    Если вы не хотите синхронизировать на карте, потому что это заставит другой поток ждать создания дорогостоящего AccountContext, попробуйте это:

    • Создайте новый класс: AccountContextBuilder: класс unexpensive-to-create, который создает дорогой AccountContext . Этот класс будет содержать метод builder для создания AccountContext или возврата ранее созданного.
    • Сделайте так, чтобы ваша карта содержала экземпляры AccountContextBuilder , а не AccountContext .
    • Синхронизация на карте (в любом случае вам нужно синхронизировать ее), на этот раз она не будет наказывать другие потоки, потому что вы собираетесь создать объект «cheap» builder.
    • Наконец, поток использует этот конструктор для получения доступа к AccountContext, таким образом, другие потоки не наказываются из-за других AccountContexts.

    Как указал @morgano, это работает только в том случае, если account является одним и тем же экземпляром для всех равных учетных записей. Кроме того, Map должен быть потокобезопасным — что часто означает использование ConcurrentHashMap или аналогичного. Если ваша карта не является потокобезопасной, то get в первой строке не является потокобезопасной — много плохих вещей может пойти не так.

    Одна вещь, которую вы можете сделать, это полосовать ваши замки. Создайте массив из N объектов (буквально Object отлично). Если вам нужна блокировка для блока synchronized , получите ее из account.hashCode() % locksArray.length , а затем синхронизируйте на этом объекте. Это означает, что вы сможете создать много AccountContext s параллельно, пока их Account S имеют разные hashCode() % N . В среднем это должно дать вам хорошую производительность; очевидно, предполагается, что Account имеет подходящее переопределение hashCode() .

    Наконец, это незначительная вещь, но в блоке синхронизации, где у вас есть:

    Я бы так и сделал:

    Тогда вам не нужен блок else .

    Похожие вопросы:

    Похоже, что ленивая инициализация свойств является частым шаблоном проектирования в JavaFX. Например, реализация с меткой OpenJFX содержит следующий фрагмент кода: public final StringProperty.

    Я наткнулся на класс singleton <ленивая инициализация>. Код выглядит следующим образом // Singleton reference for this class private static volatile FileProperties INSTANCE = null; public static.

    Я пытаюсь выучить Hibernate, я прошел через hibernate lazy initialization . У меня есть несколько разъяснений относительно lazy initialization. Прежде всего, что такое так называемая ленивая.

    Читать еще:  Что делать если на компьютере восстановление запуска

    мне нужна потокобезопасная карта, у меня есть что-то вроде этого: (я очень новичок в java) public static class Manager < static < //something wrong here, doesn't compile list = new.

    В главе 11 книги Чистый код: руководство по программному мастерству Agile дядя Боб говорит, что следующая ленивая инициализация не является чистым кодом. Он берет на себя две обязанности и имеет.

    Что такое ленивая инициализация. вот код, который я получил после поиска в google. class MessageClass < public string Message < get; set; >public MessageClass(string message) < this.Message =.

    Что такое ленивая инициализация объектов? Как вы это делаете и каковы преимущества?

    Я читал о потокобезопасной ленивой инициализации,и я смотрю на реализацию метода hashCode в классе String. По-видимому, этот метод потокобезопасен, я сделал свою собственную версию для другого.

    Может ли кто-нибудь объяснить, как происходит ленивая инициализация в следующем коде шаблона singleton? public class Singleton < private static Singleton INSTANCE = null; private Singleton() <>.

    Похоже, ленивая инициализация-это здорово. Я понимаю концепцию и протестировал свой код, и я не вижу никаких лагов. Тогда возникает вопрос, почему не всегда использовать ленивую инициализацию? Какой.

    Подводные камни Singleton: почему самый известный шаблон проектирования нужно использовать с осторожностью

      Переводы, 17 сентября 2016 в 23:33

    Паттерн “Одиночка” — пожалуй, самый известный паттерн проектирования. Тем не менее, он не лишен недостатков, поэтому некоторые программисты (например, Егор Бугаенко) считают его антипаттерном. Разбираемся в том, какие же подводные камни таятся в Singleton’е.

    Определение паттерна

    Само описание паттерна достаточно простое — класс должен гарантированно иметь лишь один объект, и к этому объекту должен быть предоставлен глобальный доступ. Скорее всего, причина его популярности как раз и кроется в этой простоте — всего лишь один класс, ничего сложного. Это, наверное, самый простой для изучения и реализации паттерн. Если вы встретите человека, который только что узнал о существовании паттернов проектирования, можете быть уверены, что он уже знает про Singleton. Проблема заключается в том, что когда из инструментов у вас есть только молоток, всё вокруг выглядит как гвозди. Из-за этого “Одиночкой” часто злоупотребляют.

    Простейшая реализация

    Как уже говорилось выше, в этом нет ничего сложного:

    • Сделайте конструктор класса приватным, чтобы не было возможности создать экземпляр класса извне.
    • Храните экземпляр класса в private static поле.
    • Предоставьте метод, который будет давать доступ к этому объекту.

    Принцип единственной обязанности

    В объектно-ориентированном программировании существует правило хорошего тона — “Принцип едиственной обязанности” (Single Responsibility Principle, первая буква в аббревиатуре SOLID). Согласно этому правилу, каждый класс должен отвечать лишь за один какой-то аспект. Совершенно очевидно, что любой Singleton-класс отвечает сразу за две вещи: за то, что класс имеет лишь один объект, и за реализацию того, для чего этот класс вообще был создан.

    Принцип единственной обязанности был создан не просто так — если класс отвечает за несколько действий, то, внося изменения в один аспект поведения класса, можно затронуть и другой, что может сильно усложнить разработку. Так же разработку усложняет тот факт, что переиспользование (reusability) класса практически невозможно. Поэтому хорошим шагом было бы, во-первых, вынести отслеживание того, является ли экземпляр класса единственным, из класса куда-либо во вне, а во-вторых, сделать так, чтобы у класса, в зависимости от контекста, появилась возможность перестать быть Singleton’ом, что позволило бы использовать его в разных ситуациях, в зависимости от необходимости (т.е. с одним экземпляром, с неограниченным количество экземпляров, с ограниченным набором экземпляров и так далее).

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

    Один из главных минусов паттерна “Одиночка” — он сильно затрудняет юнит-тестирование. “Одиночка” привносит в программу глобальное состояние, поэтому вы не можете просто взять и изолировать классы, которые полагаются на Singleton. Поэтому, если вы хотите протестировать какой-то класс, то вы обязаны вместе с ним тестировать и Singleton, но это ещё полбеды. Состояние “Одиночки” может меняться, что порождает следующие проблемы:

    • Порядок тестов теперь имеет значение;
    • Тесты могут иметь нежелательные сторонние эффекты, порождённые Singleton’ом;
    • Вы не можете запускать несколько тестов параллельно;
    • Несколько вызовов одного и того же теста могут приводить к разным результатам.

    На эту тему есть отличный доклад с “Google Tech Talks”:

    Скрытые зависимости

    Обычно, если классу нужно что-то для работы, это сразу понятно из его методов и конструкторов. Когда очевидно, какие зависимости есть у класса, гораздо проще их предоставить. Более того, в таком случае вы можете использовать вместо реально необходимых зависимостей заглушки для тестирования. Если же класс использует Singleton, это может быть совершенно не очевидно. Всё становится гораздо хуже, если экземпляру класса для работы необходима определённая инициализация (например, вызов метода init(. ) или вроде того). Ещё хуже, если у вас существует несколько Singleton’ов, которые должны быть созданы и инициализированы в определённом порядке.

    Загрузчик класса

    Если говорить о Java, то обеспечение существования лишь одного экземпляра класса, которое так необходимо для Singleton, становится всё сложнее. Проблема в том, что классическая реализация не проверяет, существует ли один экземпляр на JVM, он лишь удостоверяется, что существует один экземпляр на classloader. Если вы пишете небольшое клиентское приложение, в котором используется лишь один classloader, то никаких проблем не возникнет. Однако если вы используете несколько загрузчиков класса или ваше приложение должно работать на сервере (где может быть запущено несколько экземпляров приложения в разных загрузчиках классов), то всё становится очень печально.

    Десериализация

    Ещё один интересный момент заключается в том, что на самом деле стандартная реализация Singleton не запрещает создавать новые объекты. Она запрещает создавать новые объекты через конструктор. А ведь существуют и другие способы создать экземпляр класса, и один из них — сериализация и десериализация. Полной защиты от намеренного создания второго экземпляра Singleton’а можно добиться только с помощью использования enum’а с единственным состоянием, но это — неоправданное злоупотребление возможностями языка, ведь очевидно, что enum был придуман не для этого.

    Потоконебезопасность

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

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

    • Первый поток обращается к getInstance() , когда объект ещё не создан;
    • В это время второй тоже обращается к этому методу, пока первый ещё не успел создать объект, и сам создаёт его;
    • Первый поток создаёт ещё один, второй, экземпляр класса.

    Разумеется, можно просто пометить метод как synchronised , и эта проблема исчезнет. Проблема заключается в том, что, сохраняя время на старте программы, мы теперь будем терять его каждый раз при обращении к Singleton’у из-за того, что метод синхронизирован, а это очень дорого, если к экземпляру приходится часто обращаться. А ведь единственный раз, когда свойство synchronised действительно требуется — первое обращение к методу.

    Есть два способа решить эту проблему. Первый — пометить как synchronised не весь метод, а только блок, где создаётся объект:

    Не забывайте, что это нельзя использовать в версии Java ниже, чем 1.5, потому что там используется иная модель памяти. Также не забудьте пометить поле instance как volatile .

  • Ссылка на основную публикацию
    Adblock
    detector