Java singleton inner class

Реализация Singleton в JAVA

В этой статье я хочу затронуть тему одного из наиболее распространенных паттернов объектно-ориентированного программирования – Singleton. Но в данном случае я не буду описывать преимущества/недостатки и области применения этого паттерна, а попытаюсь изложить свой взгляд на его имплементацию в JAVA.

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

Область применения
1.) В системе должно существовать не более одного экземпляра заданного класса.
2.) Экземпляр должен быть легко доступен для всех клиентов данного класса.
3.) Создание объекта on demand, то есть, когда он понадобится первый раз, а не во время инициализации системы.

Реализация (JAVA):
На данный момент существуют несколько вариантов реализации со своими недостатками и преимуществами. В них мы и попробуем сейчас разобраться.

Вариант первый

Вариант первый – самый простой, который приходит в голову сразу после понимания проблемы.

У этого решения есть единственный недостаток – оно не работает в многопоточной среде и поэтому не подходит в большинстве случаев. Решение подходит исключительно для однопоточных приложений.

Не беда, скажите вы, и предложите следующее решение.

Вариант второй

Вариант второй:

И вы будете правы, так как проблему многопоточности мы решили, но потеряли две важные вещи:
1. Ленивую инициализацию (Объект instance будет создан classloader-ом во время инициализации класса)
2. Отсутствует возможность обработки исключительных ситуаций(exceptions) во время вызова конструктора.

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

Далее возникают 2 варианта решения.
1.) Использование внутреннего класса(решение Била Пью(Bill Pugh) “Initialization on Demand Holder”).
2.) Использование синхронизации.

Вариант третий

Вариант третий:
“Initialization on Demand Holder”

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

Синхронизация
Этой части я хотел бы уделить особое внимание. Можно было бы подойти к данному вопросу с заголовком «synchronized – мифы и реальность».

И так, самый прямолинейный метод.

Вариант четвертый

Вариант четвертый:

У этого варианта есть только один недостаток. Синхронизация полезна только один раз, при первом обращении к getInstance(), после этого каждый раз, при обращении этому методу, синхронизация просто забирает время. Что можно сказать по этому поводу? Ну, во-первых, если вызов getInstance() не происходит достаточно часто (что значит «достаточно часто» решать вам), то этот метод имеет преимущество перед остальными – прост, понятен, лениво инициализируется, дает возможность обрабатывать исключительные ситуации в конструкторе. А во-вторых, синхронизация в Java перестала быть обременительно медленной настолько, насколько ее боятся. Ну что еще для счастья надо?

Теперь рассмотрим вариант синхронизированного решения, в котором попытаемся решить проблему, возникшую в предыдущем варианте.

Double-Checked Locking

Наиболее распространенный способ — «Double-Checked Locking». В своем оригинальном варианте:

Не работает! Почему? Это отдельная тема, но для интересующихся могу посоветовать прочитать эту статью http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html.

Но не надо совсем отчаиваться, в JAVA 5 проблему решили, используя модификатор volatile. На данный момент решение выглядит так:

Вариант пятый

Вариант пятый:

Не смотря на то, что этот вариант выглядит как идеальное решение, использовать его не рекомендуется т.к. товарищ Allen Holub заметил, что использование volatile модификатора может привести к проблемам производительности на мультипроцессорных системах. Но решать все же вам.

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

Подводные камни
1. Наследование
В подавляющем большинстве случаев в Singleton классах наследование не нужно и, более того, излишне и является следствием over-design. Да и реализация наследования имеет определенные сложности, учитывая, что и сам instance и метод getInstance() статические.
Поэтому, я рекомендую использовать модификатор final и запретить наследование данного класса, если нет особой необходимости в обратном.

2. Две и более виртуальных машины
Каждая виртуальная машина создает свою копию Singleton объекта. И хотя на первый взгляд это выглядит очевидным, во многих распределенных системах, таких как EJB, JINI и RMI все не так просто. Когда промежуточные уровни скрывают (делают прозрачными) распределенные технологи, бывает трудно сказать, где и когда инициализирован объект.
3.Различные Class Loader-ы
Когда 2 class loader-а загружают класс, каждый из них может создать свою копию Singleton-а(в тех случаях, когда instance инициализируется class loader-ом ). Это особенно актуально в использовании сервлетов(servlet), так как в некоторых имплементациях серверов приложений(application server) каждый сервлет имеет свой class loader.

Существует еще ряд проблем, которые менее актуальны (такие как технология reflection и имплементация интерфейсов Cloneable и Serializable), и не будут мною рассмотрены в силу своей экзотичности в сфере применения Singleton классов. Но, в любом случае, с радостью отвечу на любые вопросы к этому материалу.

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

Источник

Паттерны проектирования: Singleton

Java-университет

Паттерны проектирования: Singleton - 1

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

Что такое синглтон?

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

Варианты реализации

  • Ленивая инициализация: когда класс загружается во время работы приложения именно тогда, когда он нужен.
  • Простота и прозрачность кода: метрика, конечно, субъективная, но важная.
  • Потокобезопасность: корректная работа в многопоточной среде.
  • Высокая производительность в многопоточной среде: потоки блокируют друг друга минимально, либо вообще не блокируют при совместном доступе к ресурсу.
  • Не ленивая инициализация: когда класс загружается при старте приложения, независимо от того, нужен он или нет (парадокс, в мире IT лучше быть лентяем)
  • Сложность и плохая читаемость кода. Метрика также субъективная. Будем считать, что если кровь пошла из глаз, реализация так себе.
  • Отсутствие потокобезопасности. Иными словами, “потокоопасность”. Некорректная работа в многопоточной среде.
  • Низкая производительность в многопоточной среде: потоки блокируют друг друга все время либо часто, при совместном доступе к ресурсу.

Код

 public class Singleton < private static final Singleton INSTANCE = new Singleton(); private Singleton() < >public static Singleton getInstance() < return INSTANCE; >> 
  • Простота и прозрачность кода
  • Потокобезопасность
  • Высокая производительность в многопоточной среде
  • Не ленивая инициализация.
 public class Singleton < private static Singleton INSTANCE; private Singleton() <>public static Singleton getInstance() < if (INSTANCE == null) < INSTANCE = new Singleton(); >return INSTANCE; > > 
 public class Singleton < private static Singleton INSTANCE; private Singleton() < >public static synchronized Singleton getInstance() < if (INSTANCE == null) < INSTANCE = new Singleton(); >return INSTANCE; > > 
  • Ленивая инициализация.
  • Потокобезопасность
  • Низкая производительность в многопоточной среде
 public class Singleton < private static Singleton INSTANCE; private Singleton() < >public static Singleton getInstance() < if (INSTANCE == null) < synchronized (Singleton.class) < if (INSTANCE == null) < INSTANCE = new Singleton(); >> > return INSTANCE; > > 
  • Ленивая инициализация.
  • Потокобезопасность
  • Высокая производительность в многопоточной среде
  • Не поддерживается на версиях Java ниже 1.5 (в версии 1.5 исправили работу ключевого слова volatile)
 public class Singleton < private Singleton() < >private static class SingletonHolder < public static final Singleton HOLDER_INSTANCE = new Singleton(); >public static Singleton getInstance() < return SingletonHolder.HOLDER_INSTANCE; >> 
  • Ленивая инициализация.
  • Потокобезопасность.
  • Высокая производительность в многопоточной среде.
  • Для корректной работы необходима гарантия, что объект класса Singleton инициализируется без ошибок. Иначе первый вызов метода getInstance закончится ошибкой ExceptionInInitializerError , а все последующие NoClassDefFoundError .
Реализация Ленивая инициализация Потокобезопасность Скорость работы при многопоточности Когда использовать?
Simple Solution + Быстро Никогда. Либо когда не важна ленивая инициализация. Но лучше никогда.
Lazy Initialization + Неприменимо Всегда, когда не нужна многопоточность
Synchronized Accessor + + Медленно Никогда. Либо когда скорость работы при многопоточности не имеет значения. Но лучше никогда
Double Checked Locking + + Быстро В редких случаях, когда нужно обрабатывать исключения при создании синглтона. (когда неприменим Class Holder Singleton)
Class Holder Singleton + + Быстро Всегда, когда нужна многопоточность и есть гарантия, что объект синглтон класса будет создан без проблем.

Плюсы и минусы паттерна Singleton

  1. Дает гарантию, что у класса будет всего один экземпляр класса.
  2. Предоставляет глобальную точку доступа к экземпляру данного класса.
  1. Синглтон нарушает SRP (Single Responsibility Principle) — класс синглтона, помимо непосредственных обязанностей, занимается еще и контролированием количества своих экземпляров.
  2. Зависимость обычного класса или метода от синглтона не видна в публичном контракте класса.
  3. Глобальные переменные это плохо. Синглтон превращается в итоге в одну здоровенную глобальную переменную.
  4. Наличие синглтона снижает тестируемость приложения в целом и классов, которые используют синглтон, в частности.

Дополнительное чтение:

Источник

Читайте также:  Where java is used in hadoop
Оцените статью