Да, вы должны абсолютно синхронизировать (или использовать лучшую идиому, такую как Singleton Holder idiom). В противном случае вы рискуете несколькими потоками, которые инициализируют ваш объект несколько раз (а затем впоследствии используют разные экземпляры).
Рассмотрим последовательность событий, как это:
- Поток А входит
getMyObj()
и видит, что obj == null
- Thread B входит
getMyObj()
и видит, что obj == null
- Поток А конструирует
new MyObj()
- давайте назовем его objA
- Тема B конструкции a
new MyObj()
- назовем это objB
- Поток А назначает
objA
к obj
- резьбы Б назначает
objB
к obj
(который не null
больше в этой точке, так что ссылка на objA
, присвоенный резьбы A, перезаписывается)
- Поток А выходит
getMyObj()
и начинается использовать objA
- Thread B выходит
getMyObj()
и начинает использовать objB
Этот сценарий может произойти с любым количеством потоков. Обратите внимание, что, хотя здесь для простоты я принял строгий порядок событий, в реальной многопоточной среде события 1-2, 3-4 и/или 7-8 могут частично или полностью перекрываться во времени, не меняя конца результат.
Пример к держателю идиомы:
public class Something {
private Something() {
}
private static class LazyHolder {
public static final Something INSTANCE = new Something();
}
public static Something getInstance() {
return LazyHolder.INSTANCE;
}
}
Это гарантированно безопасна, так как INSTANCE
является final
. Модель памяти Java гарантирует, что поля final
инициализируются и отображаются корректно для любого количества потоков при загрузке содержащего класса. Так как LazyHolder
- private
, на который ссылается только getInstance()
, он будет загружен только тогда, когда вызывается getInstance()
. И в этот момент, INSTANCE
инициализируется в фоновом режиме и безопасно публикуется JVM.
Я не могу пройти маршрут держателя: он заставляет singleton на моем внешнем классе, который требует гораздо большего мышления и изменений в общей структуре, которую я разрабатываю. Проблемой, которую я должен был решить, было создание атома статического экземпляра. Я использовал статический метод и объявил его ('obj') статическим окончанием, чтобы обеспечить атомное присвоение и одновременную безопасность. –
@atc: Нет, маршрут держателя не заставляет внешний класс быть одиночным. Не совсем понятно, что вы имеете в виду здесь ... –
Для ясности: эта переменная 'static volatile' изначально была на Factory-объекте, которая ввела его во время выполнения в экземпляры, которые он отвечал за создание. Это связано с детальностью реализации упомянутого staic var ('MyObj' выше). Использование конструктора 'private' для удовлетворения идиомы держателя означало, что я не мог сохранить преимущества наследования для Factory объекта. Вместо того, чтобы улаживать идиому держателя здесь, я думал, что поеду на синхронизацию с двойной проверкой, поскольку это имеет наименьшее трение с кодовой базой и было доказано, что оно работает. –