Comments 85
И чем же, по-вашему, вариант с enum не «ленивый»?
По моему вариант с enum не «ленивый». Т.е. Объект создастся сразу, как только загрузится клаас. А если например, у нас есть статические методы в этом enume? Вызвав их хоть раз, мы создадим инстанс. Получается не ленивый.
Перечитал Блоха, ссылки. Ни где не нашел упоминания о том, что вариант с Enum — ленивый.
Все верно, любой класс, в том числе enum, инициализируется при вызове статического метода класса. https://stackoverflow.com/a/64053573/
Статическая инициализация класса обычно происходит непосредственно перед первым случаем одного из следующих событий:
создается экземпляр класса,
вызывается статический метод класса,
присваивается статическое поле класса,
используется непостоянное статическое поле
Но что должно быть внутри класса? Как позаботиться о том, что в коде его не создадут несколько раз?
Википедия со ссылкой на Joshua Bloch «Effective Java, Second Edition», p. 283-284 предлагает избегать лишних чтений volatile полей, якобы это вредит перформансу:
en.wikipedia.org/wiki/Double-checked_locking
// Works with acquire/release semantics for volatile in Java 1.5 and later
// Broken under Java 1.4 and earlier semantics for volatile
class Foo {
private volatile Helper helper;
public Helper getHelper() {
Helper localRef = helper;
if (localRef == null) {
synchronized (this) {
localRef = helper;
if (localRef == null) {
helper = localRef = new Helper();
}
}
}
return localRef;
}
// other functions and members...
}
«Note the local variable „localRef“, which seems unnecessary. The effect of this is that in cases where helper is already initialized (i.e., most of the time), the volatile field is only accessed once (due to „return localRef;“ instead of „return helper;“), which can improve the method's overall performance by as much as 25 percent.[7]»
Противоречий нет конечно, возможно они имели ввиду как раз таки общий случай, а не x86, но тогда вопрос, если на x86 мы гарантировано не получаем прироста производительности, то откуда они взяли цифру 25% и на какой платформе они получили такой прирост, и хорошо бы им это упомянуть, в силу того, что x86 всё таки значительно популярнее всего остального.
www.javaperformancetuning.com/news/qotm051.shtml
www.javaperformancetuning.com/news/qotm030.shtml
Если в кратце, синхронизация volatile стоит столько-же или дешевле чем monitor enter/monitor exit. Опять же, думаю играет не малую роль то, что при использовании syncronized приходиться синхронизировать и обновлять все копии переменных (shared variables) — а это нагрузка на кеш L2/память. А volatile перменная — одна в main memory.
Боллее того, эти числа могут сильно различаться в зависимости от платформы (NUMA, HT и т.д.).
Да и «Остроумно» я бы скорее отнёс к минусам :)
Вывод: если правильно подобрать реализацию шаблона можно получить ускорение (speed up) от 2х до 4х.
Ускорение чего именно можно получить? Ускорение обращения к классу риспользующему паттерн синглтон? А подобные обращения занимают в реальных приложениях 1% или может быть 0.000001%?
Примечательно, что разработчики Java Class Library выбрали наиболее простой способ реализации шаблона — Syncronized Accessor.
Java началась 20 лет назад и основные классы разрабатывались ещё до появления моды на шаблоны проектирования.
Что будет если в Java Class Library правильно написать все Singleton классы?
Ничего не будет. Никто даже не заметит.
Хороший пример того как нужно писать можно посмотреть в классе java.awt.AppContext.
Да, 20 лет назад. Но это не значит, что не надо развиваться и пытаться исправить ошибки и наследие былых лет.
Микробенчмарки все заметят :) Особенно мне кажется изменения коснутся Swing/AWT.
Но это не значит, что не надо развиваться и пытаться исправить ошибки и наследие былых лет.
это не ошибки. Никакого заметного влияния на производительность это не имеет (а уж тем более в Свинге) — значит незачем что-то менять и тратить деньги. Ресурсы на производство ПО ещё 20 лет назад умели считать.
Говорил про свинг не просто так. Долго ковырял его и находил «неправильные синглтоны». На сколько часто они используются сказать не могу.
if (localInstance == null) {
synchronized (Singleton.class) {
instance = localInstance;
if (instance == null) {
localInstance = instance = new Singleton();
}
}
}
Ошибка в instance = localInstance; ведь между if (localInstance == null) и захватом монитора на Singleton.class другой поток может успеть проинициализировать instance.
Не ясно, зачем двойное присвание, ведь локальная переменная больше не будет использована.
Минус «Поддерживается только с JDK 1.5» на самом деле никакой не минус, JDK1.4 уже не поддерживается. А там, где оно все же используется, 99% будет какой-нибудь legacy сервер приложений, где синглтон нужно реализовывать иначе.
1 Singleton localInstance = instance;
2 if (localInstance == null) {
3 synchronized (Singleton.class) {
4 instance = localInstance;
5 if (instance == null) {
6 localInstance = instance = new Singleton();
7 }
8 }
9 }
10 return instance;
Пусть instance == null
Поток 1 выполняет строчку 1 и останавливается, поток 2 получает управление, выполняет строки 1-10 и останавливается, инстанс уже создан. Поток 1 снова получает управление, доходит до строки 4 и перезаписывает поле, уже инициализированное потоком 2 — записывает туда localInstance (которое у потока 1 == null). Далее проверка в строке 5 успешна и выполняется строка 6.
Что не так?
public static Singleton getInstance()
{
if (instance == null)
{
synchronized(Singleton.class) {
Singleton inst = instance;
if (inst == null)
{
synchronized(Singleton.class) {
inst = new Singleton();
}
instance = inst;
}
}
}
return instance;
}
В этом варианте есть локальная переменная, но она создаётся внутри синхронайзд блока, и не убивает логику как тут.
www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
Зачем нужно присваивание, не ясно.
В Effective Java на странице 284 есть объяснение про чтение, но нет объяснения про запись в локальную переменную.
Зачем в нее записывать созданный инстанс?
Автор, вы собираетесь править ошибку в варианте 2? Или объясните, где я не прав.
Метод ClassLoader.loadClass() — synchronized, но я без понятия, как грузятся классы, если к ним обращаться в коде…
Однако фокус состоит в том, что в процессе исполнения Java-приложения после инициализации класса JVM может заново перекомпилировать метод, вызывающий getInstance(), избавившись от ненужного class initialization barrier и оптимизировав таким образом вызов статического метода.
* Сам за годы работы ни разу не писал синглтонов.
Конструкторы писать, статья все-таки не для новичка (новчки прочитают и на википедии). Статья ориентировалась на людей, которые использовали шаблон и у них остались вопросы о том, почему он не работает.
Вот какраз хотел написать в чем же разница между этими примерами и статичными классами. Уже было подумал что очередной хайп
Мне просто очень нравится задавать этот вопрос на интервью :)
Не бойтесь, он вам ничего не сделает.
private static final Object lock = new Object();
Иначе кто-то другой может залочиться на Singleton.class, и быть беде.
public class Singleton {
private static Singleton instance = null;
private static final Object lock = new Object();
private static boolean isInitialized = false;
public static Singleton getInstance() {
if (!isInitialized) {
synchronized (lock) {
if (instance == null) {
instance = new Singleton();
isInitialized = true;
}
}
}
return instance;
}
}
Ситуация не такая редкая. Сам с такой столкнулся и выявил проблему только после долгого дебага ;)
Вот статья на тему того, как сделать абслютный синглтон, избегающий этой проблемы: surguy.net/articles/communication-across-classloaders.xml
dlang.ru/low-lock-singletons-v-d
public static class SingletonHolder
Похоже что он должен быть private - смысл в том что ссылки на него должны существовать в программе только в рамках метода getInstance
- Невозможно использовать для не статических полей класса
Может кто-нибудь пояснить что это значит? Вообще не понял что имелось ввиду.
Правильный Singleton в Java