Синхронизированные блоки в конструкторах

#java #multithreading

#java #многопоточность

Вопрос:

У меня есть класс со статическим переменным, например

 private static Object sMyStaticVar;
  

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

 if(sMyStaticVar == null) sMyStaticVar = new CustomObject(someRuntimeObject);
  

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

 private static Object sMyStaticVar = new CustomObject(someRuntimeObject);
  

мой вопрос в том, безопасна ли инициализация статического объекта var в потоке конструктора? Мои инстинкты говорят мне, что это не так, и я должен синхронизировать, используя тип класса, не относящийся к среде выполнения, в качестве блокировки, как показано ниже

 synchronized(MyClass.class)
{
    if(sMyStaticVar == null) sMyStaticVar = new CustomObject(someRuntimeObject);
}
  

(в отличие от типа среды выполнения, полученного из getClass() )

но поскольку мои инстинкты обычно ошибочны, я был бы признателен, если бы кто-нибудь мог пролить некоторый свет на это для меня!

Комментарии:

1. (Изменяемая статика — действительно плохая идея.)

2. как так? в этом случае я только создаю объект и в любом случае присваиваю его статической переменной.

Ответ №1:

Если он статический, вы не должны назначать его в конструкторе. Создайте статический метод инициализатора, который это делает public static synchronized void initialize(someRuntimeObject) .

Обратите внимание на synchronized ключевое слово: это то же самое, что синхронизация на MyClass.class

Комментарии:

1. и вызвать этот метод из конструктора? Будет ли это фактически таким же, как мое предложение выше, но немного аккуратнее?

2. нет, не вызывайте это в конструкторе. Вызовите его из любого места, где вы бы вызывали конструктор. Конструктор предназначен для каждого объекта, а ваше поле static — для каждого класса.

3. вы хотите сказать, что я должен вызывать его одновременно с созданием объекта? Почему это не сработало бы, если бы я вызвал его из конструктора?

4. Я не хочу каждый раз вызывать этот метод инициализации перед созданием нового объекта такого типа.

5. @Dori как я уже отмечал, static поля для каждого класса. Это не имеет никакого отношения к конструктору. Вы не должны смешивать эти две вещи. Либо сделайте его нестатическим и инициализируйте его в конструкторе, либо сделайте его статическим и инициализируйте его один раз

Ответ №2:

Вы правы, следующее открыто для условий гонки:

 if(sMyStaticVar == null) sMyStaticVar = new CustomObject(someRuntimeObject);
  

Два потока могут sMyStaticVar одновременно проверять, просматривать null , создавать два объекта и т. Д…

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

 private static Object sMyStaticVar;
private static Object sMyStaticVarLock = new Object();
  

Затем в конструкторе:

 synchronized(sMyStaticVarLock)
{
    if(sMyStaticVar == null) sMyStaticVar = new CustomObject(someRuntimeObject);
}
  

Комментарии:

1. будет ли синхронизация с блокировкой, которую вы описали выше, иметь какое-либо преимущество перед synchronized (MyClass.class )?

2. @Dori: Если вы не знаете наверняка, кто еще может синхронизировать MyClass.class и с какой целью, вы не можете знать, на какую блокировку вы подписываетесь. Здесь вы точно знаете, что блокировка используется ровно для одной цели.

Ответ №3:

Эта синхронизация необходима, но этого недостаточно для обеспечения потокобезопасности.

Вам также необходимо обеспечить видимость значения поля при обращении к нему. Итак, вы должны либо объявить это поле как volatile , либо добавить ту же синхронизацию для каждого доступа к этому полю.