Различные способы написания singleton в Java

Классик написания singleton в java выглядит так:

public class SingletonObject { private SingletonObject() { } public static SingletonObject getSingletonObject() { if (ref == null) // it's ok, we can call this constructor ref = new SingletonObject(); return ref; } private static SingletonObject ref; } 

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

Но я предпочитаю писать это как:

 public class SingletonObject { private SingletonObject() { // no code req'd } public static SingletonObject getSingletonObject() { return ref; } private static SingletonObject ref = new SingletonObject(); } 

который, я думаю, более краткий, но, как ни странно, я не видел никакого примера кода, написанного таким образом, есть ли какие-либо плохие последствия, если я написал свой код таким образом?

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

Во второй форме ваш синглтон загружен, и на самом деле это предпочтительная форма (и первая из них не является streamобезопасной, поскольку вы упомянули ее сами). Нежелательная загрузка – это не плохо для производственного кода, но есть контексты, где вам может понадобиться ленивая загрузка ваших синглетов, как об этом говорил автор Guice, Боб Ли, в Lazy Loading Singletons, который я цитирую ниже:

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

До Java 1.5 я лениво загружал одиночные игры, используя обычную старую синхронизацию, простую, но эффективную:

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

Изменения в модели памяти в 1.5 включили идиому постыдного Double-Checked Locking (DCL). Для реализации DCL вы проверяете поле volatile в общем пути и только при необходимости синхронизируете:

 static volatile Singleton instance; public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) instance == new Singleton(); } } return instance; } 

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

Представьте мое удивление сегодня, когда Джереми Мэнсон указал мне на идиому Инициализация по требованию (IODH), которая требует очень небольшого кода и имеет нулевые накладные расходы синхронизации. Ноль, как в еще быстрее, чем volatile . Для IODH требуется такое же количество строк кода, как обычная старая синхронизация, и это быстрее, чем DCL!

IODH использует ленивую инициализацию classа. JVM не будет запускать статический инициализатор classа, пока вы на самом деле не коснетесь чего-то в classе. Это относится и к статическим вложенным classам. В следующем примере JLS гарантирует, что JVM не будет инициализировать instance до тех пор, пока кто-то не вызовет getInstance() :

 static class SingletonHolder { static Singleton instance = new Singleton(); } public static Singleton getInstance() { return SingletonHolder.instance; } 

[…]

Обновление: кредит, при котором должен быть предоставлен кредит. Эффективная Java (авторское право 2001 г.) подробно описывает этот шаблон под пунктом 48. Далее следует указать, что вам все равно придется использовать синхронизацию или DCL в нестатических контекстах.

Я также переключил обработку singleton в своих рамках от синхронизации до DCL и увидел еще 10% -ное повышение производительности (по сравнению с тем, как я начал использовать быстрое reflection cglib). Я использовал только один stream в своем микро-бенчмарке, поэтому усиление параллелизма может быть еще большим, учитывая, что я заменил жестко заблокированную блокировку с относительно мелким зернистым изменчивым полевым доступом.

Обратите внимание, что Джошуа Блох теперь рекомендует (начиная с Effective Java, 2nd ed) реализовать синглтоны, используя одноэлементное enum как указал Джойник .

Ну, в последнем случае объект singleton создается до того, как он когда-либо понадобится, но в большинстве случаев это, вероятно, не ужасно плохо.

Кстати, Джошуа Блох рекомендует (в « Эффективной Java» , 2-е изд., П. 3), реализуя синглтоны с использованием одноэлементного enums:

 public enum SingletonObject { INSTANCE; } 

Он дает следующее обоснование:

[…] он более лаконичен, обеспечивает бесплатную сериализацию и предоставляет отличную гарантию против множественного экземпляра, даже в условиях сложных атак сериализации или отражения. Хотя этот подход еще не принят, одноэлементный тип enums является наилучшим способом реализации одноэлементного подхода.

Я бы сказал, что последний код является более стандартным. Ваша первая версия не является streamобезопасной. Способы обеспечения безопасности streamа include в себя синхронизацию при каждом доступе или очень тщательное использование блокировки с двойным проверкой (что безопасно с моделью памяти Java 5, если вы это исправите).

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

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

В «Эффективной Java» есть более подробная информация об этом, но у меня нет его, чтобы найти номер товара, я боюсь.

Я думаю, ваша проблема в том, что вы смешиваете одиночную и ленивую инициализацию. Один синглтон может быть реализован с различными страtagsями инициализации :

  • инициализация загрузки classов
  • ленивая инициализация, использующая двойную проверку блокировки
  • ленивая инициализация с единой проверкой (с возможной повторной инициализацией)
  • ленивая инициализация, использующая загрузчик classов (идион classа владельца)

Все эти подходы обсуждаются в « Эффективной Java». Второй пункт 71: разумно используйте ленивую инициализацию .

Различные способы реализации singleton-шаблона в java заключаются в следующем.

 public class SingletonClass { private SingletonClass= null; public static SingletonClass getInstance() { if(SingletonClass != null) { SingletonClass = new SingletonClass(); } } } 

Это не является streamобезопасным. Ниже приведены thread safe реализация шаблона одноэлементного дизайна

1. Драконовская синхронизация

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

2. Двойная проверка синхронизации

Справка

 private static final Object lock = new Object(); private static volatile YourObject instance; public static YourObject getInstance() { YourObject r = instance; if (r == null) { synchronized (lock) { // While we were waiting for the lock, another r = instance; // thread may have instantiated the object. if (r == null) { r = new YourObject(); instance = r; } } } return r; } 

3. Идиомация по требованию владельца по требованию

Справка

 public class Something { private Something() {} private static class LazyHolder { static final Something INSTANCE = new Something(); } public static Something getInstance() { return LazyHolder.INSTANCE; } } 

4. Другой использует перечисление

 public enum Singleton { SINGLE; public void myMethod(){ } } 

второй способ позволит решить проблему многопоточности, но всегда будет создавать Singleton, даже если вам это не понадобится. Лучший способ сделать это – создать class Nested.

 public class singleton { private singleton() { System.out.println("I'am called only when it's needed"); } static class Nested { Nested() {} private static final singleton instance = new singleton(); } public static singleton getInstance() { return Nested.instance; } public static void main(String [] args) { singleton.getInstance(); } } 

Ниже приведен лучший способ написать одноэлементный class. Попробуйте его

 public final class SingeltonTest { /** * @param args * @return */ private static SingeltonTest instance = null; private SingeltonTest() { System.out.println("Rahul Tripathi"); } public static SingeltonTest getInstance() { if (instance == null) { synchronized (SingeltonTest.class) { if (instance == null) instance == new SingeltonTest(); } } return instance; } } 
 // Lazy loading enabled as well as thread safe 

class Singleton {

  private static class SingletonHolder { public static Singleton instance = new Singleton(); } public static Singleton getInstance() { return SingletonHolder.instance; } } 

Я согласен с Anon, и в случае, когда я всегда хочу создать экземпляр синглтона, я бы использовал

 public class SingletonObject { public static SingletonObject REF = new SingletonObject(); private SingletonObject() { // no code req'd } 

}