В третьем подходе вы проверяете переменную singleton
; Вы делаете это вне любого синхронизированного блока, поэтому он не работает: здесь нет гарантии, что потоки ждут перед проверкой. Все они проверяют так быстро, как только могут, поэтому потоки 2+ могут все видеть здесь ноль, даже если один из них уже работает над созданием этого экземпляра.
Затем вы синхронизируете, конечно. Тем не менее, это волшебным образом не дает этому коду «назначать одноразовые однократные» полномочия - в конце концов, код в этом одноэлементном блоке собирается назначать вновь созданный экземпляр SingletonClass
переменной singleton
.
Два соответствующих примечания:
[1] Модель памяти java утверждает, что любое данное поле похоже на кошку Шредингера: каждый поток имеет его копию или не имеет - вплоть до модели потоков. Отдельная копия отправляется в копию каждого потока или в некоторые из них в произвольные моменты времени, и то же самое касается получения обновлений от других. Вы не можете полагаться на этот механизм, он может даже не использоваться, нет способа управлять им (кроме volatile
, который может помочь, но его немного сложно использовать правильно). Дело в том, чтобы написать свой код так, чтобы он не имел значения. Как только вы устанавливаете sh отношения «до», «/ после» между кодами, например, из-за того, что вы используете синхронизированный блок, эта произвольная природа исчезает, и вам гарантирована видимость (поэтому, если код A предшествует коду B, например, поскольку они оба синхронизируются на одном и том же объекте и A «выиграли» битву, все, что A записывает в любом месте, будет видно B, как только B начнет работать, гарантировано, потому что здесь есть отношение CA / CB).
Поместите эту нулевую проверку внутрь, и проблема внезапно исчезнет.
[2] Если все, что вы пытаетесь сделать sh, это то, что существует ровно один экземпляр SingletonClass
, вы лаять не на то дерево. Это не то, как это сделать. Это на самом деле тривиально просто. Все, что вы делаете, это одна строка:
public class SingletonClass {
public static final SingletonClass instance = new SingletonClass();
private SingletonClass() {
// ensure nobody but you can call this.
}
}
Вот и все. Вы можете подумать, что это означает, что класс инициализируется при загрузке вашего приложения, но это не так. Классы загружаются, только если запущен какой-то код, который использует класс. Предполагая, что ВСЕ случаи использования SingletonClass включают получение этого экземпляра синглтона (обычно true), это так же хорошо, как и все остальное. Если по какой-то причудливой причине причина может взаимодействовать с S C без захвата синглтона, вы все равно можете использовать этот механизм, просто используя внутренний класс:
public class SingletonClass {
private SingletonClass() {}
public static SingletonClass getInstance() {
return Inner.instance;
}
private static class Instance {
private static final SingletonClass instance = new SingletonClass();
}
}
Это гарантированно не вызывает этот конструктор, пока кто-то вызывает getInstance (), вызывает только один раз, НЕ МОЖЕТ вызывать его дважды и делает это наиболее эффективным способом.
РЕДАКТИРОВАТЬ: Форматирование.