Java. Шаблон Singleton
Шаблон не является частью языка Java, это рекомендация умных программистов для создания эффективных решений во время разработки сложных проектов.
Синглтон (singleton) это класс, у которого экземпляр создаётся только один раз. Хорошим примером такого поведения служит файловая система, система работы с видео и т.д. В Android можно привести примеры с классами OkHttpClient, Retrofit, Gson, SharedPreferences. Для реализации синглтона нужно создать закрытый конструктор и открытый статический член, который и позволяет получить доступ к единственному экземпляру класса. Например, так.
public class Single < public static final Single INSTANCE = new Single(); private Single()<>public void someMethod() < Log.i("Log", "I am someMethod"); >>
Закрытый конструктор вызывается один раз для инициализации поля INSTANCE. Открытых конструктор у класса нет, поэтому после инициализации класса будет существовать только один экземпляр Single.
Существует другой вариант, когда вместо открытого статического поля создаётся открытый статический метод, а поле становится закрытым.
public class Single < private static final Single INSTANCE = new Single(); private Single()<>public void someMethod() < Log.i("Log", "I am a someMethod"); >public static Single getInstance() < return INSTANCE; >>
Второй способ удобен тем, что вы можете позже отказаться от синглтона, но вам не придётся сильно переписывать код.
Позже был придуман третий вариант с использованием enum.
Паттерн Singleton в Java
Что такое Singleton и где его применять (а где — нет) описано в статье «Паттерн Singleton. Описание. Пример использования«. Пример реализации со статическим объектом в классе на C# можно посмотреть тут: «Singleton или статический класс?«.
1 Примитивная реализация Singleton на Java
Самое простое — использовать статическое поле, оно будет общим для класса, считай, глобальная переменная.
public final class Singleton < private static Singleton _instance = null; private Singleton() <>public static synchronized Singleton getInstance() < if (_instance == null) _instance = new Singleton(); return _instance; >>
Конструктор класса необходимо объявить с модификатором видимости private . Это предотвратит создание экземпляров класса как с помощью класса Singleton , так и с помощью его наследников. В связи с этим к объявлению класса смело можно дописать модификатор final . Метод getInstance() создаст ровно один экземпляр класса Singleton . Этот метод объявлен как synchronized . Сделано это вот почему. В многопоточных программах при одновременном вызове метода getInstance() из нескольких потоков можно создать несколько экземпляров класса Singleton . А должен остаться только один. От модификатора synchronized можно избавиться. Для этого _instance нужно проинициализировать: private static final Singleton _instance = new Singleton(), а в методе getInstance() убрать конструкцию « if «. Тогда инициализация произойдет во время загрузки класса. Но использование поздней инициализации (lazy initialization) предпочтительнее в случае, если создание экземпляра класса занимает много времени. Да и в случае ленивой инициализации есть возможность обработать возникшие исключитальные ситуации при вызове конструктора.
2 Продвинутая реализация Singleton на Java
Однако, реализовать можно так:
public class Singleton < // Private constructor prevents instantiation from other classes private Singleton() <>/** * SingletonHolder is loaded on the first execution of Singleton.getInstance() * or the first access to SingletonHolder.INSTANCE, not before. */ private static class SingletonHolder < private static final Singleton INSTANCE = new Singleton(); >public static Singleton getInstance() < return SingletonHolder.INSTANCE; >>
Эта необычная реализация гораздо лучше, чем со статическим полем внутри класса Singleton, предложил ее этот известный мужик: Bill Pugh. При помощи внутреннего класса Вы получаете ленивый объект — Singleton не инициализируется до моменты вызова метод getInstance() и потоко-безопасный Singleton. Поскольку в классе Singleton нет статических полей которые нужно инициализировать, класс беспрепятственно загрузится. Когда объект INSTANCE будет создан? — когда мы вызовем метод getInstance() , что повлечет загрузку внутреннего класса SingletonHolder и создание объекта INSTANCE . Поскольку фаза инициализации класса гарантировано (спецификацией) «не конкурентна», то у нас нет необходимости использовать synchronized и volatile .
3 Использование паттерна Singleton
Пример взят с сайла Javenue. Иногда конфигурацию программы удобно хранить в файле. Допустим, это будет простой текстовый файл «props.txt» со строками типа «ключ=значение» . Нам нужно гарантировать, что конфигурация в программе будет в единственном экземпляре. Вторую мы бы и так не создали, но нужно запретить это делать пользователю класса. Итак,
import java.util.*; import java.io.*; public class Configuration < private static Configuration _instance = null; private Properties props = null; private Configuration() < props = new Properties(); try < FileInputStream fis = new FileInputStream( new File("props.txt")); props.load(fis); >catch (Exception e) < // обработайте ошибку чтения конфигурации >> public synchronized static Configuration getInstance() < if (_instance == null) _instance = new Configuration(); return _instance; >// получить значение свойства по имени public synchronized String getProperty(String key) < String value = null; if (props.containsKey(key)) value = (String) props.get(key); else < // сообщите о том, что свойство не найдено >return value; > >
Теперь для работы с конфигурацией можно использовать конструкцию вида: String propValue = Configuration.getInstance().getProperty(propKey). Если имена свойств в «props.txt» меняться не будут, можно описать их в классе таким образом: public static final String PROP_KEY = «propKey», а значения получать так:
String propValue = Configuration.getInstance() .getProperty(Configuration.PROP_KEY).
Реализация 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». В своем оригинальном варианте:
Не работает! Почему? Это отдельная тема, но для интересующихся могу посоветовать прочитать эту статью 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
В этом посте мы обсудим, как эффективно реализовать одноэлементный шаблон в Java.
Синглтон — это просто класс, экземпляр которого создается ровно один раз. Одиночки обычно представляют уникальный системный компонент, такой как оконный менеджер или файловая система.
1. Простое решение
Мы можем просто реализовать одноэлементный шаблон, оставив конструктор закрытым и экспортировав общедоступный статический фабричный метод, который обеспечивает доступ к единственному экземпляру.
public class Singleton
// INSTANCE должен быть статическим и окончательным, чтобы предотвратить последующую модификацию
private static final Singleton INSTANCE = new Singleton ( ) ;
// Конструктор должен быть закрытым, чтобы предотвратить появление другого экземпляра класса.
private Singleton ( ) < >
// Общедоступный статический метод возвращает ту же ссылку на объект
// каждый раз, когда он вызывается
public static Singleton getInstance ( ) <
return INSTANCE ;
Все звонки на getInstance() метод возвращает ту же ссылку на объект, и никакой другой экземпляр класса никогда не будет создан из-за отсутствия общедоступного или защищенного конструктора. Частный конструктор вызывается только один раз для инициализации закрытого статического конечного поля. INSTANCE .
2. Ленивая загрузка
1. В предыдущем подходе приватный конструктор вызывается только один раз для инициализации приватного статического конечного поля. INSTANCE . Мы также можем инициализировать одноэлементный класс внутри getInstance() метод, как показано ниже:
public static Singleton getInstance ( ) < if ( INSTANCE == null ) < INSTANCE = new Singleton ( ) ; return INSTANCE ;
Это называется ленивой загрузкой. Теперь, когда getInstance() метод вызывается в первый раз, мы создаем экземпляр одноэлементного класса, вызывая его частный конструктор. Начиная со второго раза, он возвращает одну и ту же ссылку на объект. Эта реализация не является потокобезопасной. Если несколько потоков вызывают getInstance() метод одновременно, каждый поток видит INSTANCE член как null , и каждый поток может создать новый экземпляр singleton. Чтобы избежать этого, мы должны разместить наш код инициализации внутри синхронизированного блока.
2. Поскольку привилегированный клиент может рефлексивно вызывать приватный конструктор с помощью AccessibleObject.setAccessible() метод, мы должны изменить конструктор, чтобы генерировать исключение, если его попросят создать второй экземпляр, как показано ниже:
private Singleton ( ) < if ( INSTANCE != null ) < throw new IllegalStateException ( ) ;
3. Чтобы сделать одноэлементный класс сериализуемым, недостаточно добавить implements Serializable к его декларации. Чтобы гарантировать синглтон, нам нужно объявить все поля экземпляра переходными и добавить readResolve() метод для одноэлементного класса. В противном случае мы будем создавать новый экземпляр каждый раз при десериализации сериализованного экземпляра.