Первое решение (я считаю) не поточно-ориентированное.
Второе решение (я считаю) поточно-ориентированное, но может не сработать, если у вас есть сложные зависимости инициализации, в которых 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). Вся мощь языка доступна в этих инициализаторах; программисты должны проявлять осторожность. ...