Упрощенный шаблон Singleton в Java - PullRequest
4 голосов
/ 06 марта 2010

Способ реализации одноэлементного шаблона по умолчанию:

class MyClass {
  private static MyClass instance;
  public static MyClass getInstance() {
    if (instance == null) {
      instance = new MyClass();
    }
    return instance;
  }
}

В старом проекте я пытался упростить вещи:

class MyClass {
  private static final MyClass instance = new MyClass();
  public static MyClass getInstance() {
    return instance;
  }
}

Но иногда это терпит неудачу. Я просто никогда не знал, почему, и я сделал путь по умолчанию. Делая SSCCE для публикации здесь сегодня, я понял, что код работает.

Итак, я хотел бы знать мнения .. Это дополнительный код ошибки? Есть ли шанс, что второй подход вернет ноль? Я схожу с ума?

- Хотя я не знаю, является ли правильный ответ для каждого случая, это действительно интересный ответ @Alfred: Я также хотел бы отметить, что синглтоны испытывают кошмар и что, по мнению крупных парней, вы должны использовать каркас внедрения зависимостей Google.

Ответы [ 8 ]

8 голосов
/ 06 марта 2010

Рекомендованный ( Effective Java 2nd ed ) способ - создать «шаблон синглтона enum»:

enum MyClass {
    INSTANCE;

    // rest of singleton goes here
}

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

4 голосов
/ 06 марта 2010

Первое решение (я считаю) не поточно-ориентированное.

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

Оба решения позволяют кому-то создать еще один экземпляр вашего (условно) одноэлементного класса.

Более надежное решение:

class MyClass {

  private static MyClass instance;

  private MyClass() { }

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

В современной JVM стоимость приобретения блокировки минимальна, при условии, что за блокировку нет разногласий.

РЕДАКТИРОВАТЬ @Nate ставит под сомнение мое утверждение о статическом порядке инициализации, который может вызывать проблемы. Рассмотрим следующий (патологический) пример:

public ClassA {
    public static ClassB myB = ClassB.getInstance();
    public static ClassA me = new ClassA();
    public static ClassA getInstance() {
        return me;
    }
}

public ClassB {
    public static ClassA myA = ClassA.getInstance();
    public static ClassB me = new ClassB();
    public static ClassB getInstance() {
        return me;
    }
}

Существует два возможных порядка инициализации для этих двух классов. Оба приводят к тому, что статический метод вызывается до статической инициализации классов метода. Это приведет к инициализации ClassA.myB или ClassB.myA равным null.

На практике циклические зависимости между статиками менее очевидны, чем эта. Но факт остается фактом: если существует циклическая зависимость: 1) компилятор Java не сможет рассказать вам об этом, 2) JVM не сообщит вам об этом. Скорее JVM будет молча выбирать порядок инициализации, не «понимая» семантику того, что вы пытаетесь сделать ... возможно, что приведет к чему-то неожиданному / неправильному.

РЕДАКТИРОВАТЬ 2 - Это описано в JLS 12.4.1 следующим образом:

Как показано в примере в §8.3.2.3, тот факт, что код инициализации является неограниченным, позволяет создавать примеры, в которых значение переменной класса можно наблюдать, когда оно все еще имеет свое начальное значение по умолчанию, до того, как его инициализирующее выражение будет оценивается, но такие примеры на практике редки. (Такие примеры также могут быть созданы для инициализации переменной экземпляра; см. Пример в конце §12.5). Вся мощь языка доступна в этих инициализаторах; программисты должны проявлять осторожность. ...

3 голосов
/ 06 марта 2010

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

2 голосов
/ 06 марта 2010

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

1 голос
/ 07 марта 2010

Не забудьте шаблон SingletonHolder. См. этот ТАК вопрос.

1 голос
/ 06 марта 2010

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

Просто убедитесь, что объявление

private static final MyClass INSTANCE = new MyClass();

является первым статическим объявлением в вашем классе, чтобы избежать риска вызовов getInstance ()или INSTANCE до его инициализации.

1 голос
/ 06 марта 2010

Помните, вам нужно объявить закрытый конструктор, чтобы обеспечить свойство singleton.

Второй случай может быть даже проще, просто

class MyClass {
  public static final MyClass instance = new MyClass();

  private MyClass() {
      super()
  }
}

`

0 голосов
/ 06 марта 2010

Я не знаю ответов на ваши вопросы, но вот как я могу структурировать то же самое.

class MyClass {
  private static MyClass instance;

  static {
    instance = new MyClass();
  }

  private MyClass() { }

  public static MyClass getInstance() {
    return instance;
  }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...