Безопасно ли публикуются поля инъекции (@Inject)?

Когда я использую инъекцию полей в классе, например:

@Inject
private MyClass myField;

могу ли я сделать какое-либо предположение о статусе "безопасной публикации" этого поля? Или, говоря иначе, и предполагая, что сам MyClass является потокобезопасным, существуют ли какие-либо риски параллелизма, о которых я должен знать при использовании этого поля?

Мой инстинкт обычно состоит в том, чтобы создать все поля окончательными, если это возможно, но это не работает с внедрением полей. Конечно, я могу использовать внедрение конструктора, но тогда мне обычно приходится создавать дополнительный «фальшивый» конструктор без аргументов только для проксирования. Не большая проблема, но использование инъекций поля просто удобнее. Другим вариантом может быть пометка поля как изменчивого (или даже использование блокировки...), но действительно ли это необходимо?

Спецификация JSR-299, похоже, не отвечает на этот вопрос. Я использую CDI в таких реализациях, как Weld.

  • Объект, в который я вставляю будет использоваться несколькими потоками (например, @ApplicationScoped). Я хочу этот.
  • Я понимаю, что если MyClass неизменяем, безопасная публикация не вызывает беспокойства. Но я не обязательно ввожу только неизменяемые объекты.
  • Предполагается, что сам MyClass является потокобезопасным; это не моя забота. Беспокойство касается исключительно небезопасной публикации, например. возможность того, что потоки увидят наполовину сконструированные экземпляры MyClass из-за правил модели памяти Java.

person HansMari    schedule 13.12.2012    source источник
comment
Да, использование CDI иногда требует написания менее защищенного кода при использовании модификаторов доступа и final. Но я не вижу разумных сомнений в том, что безопасность потоков будет недосмотром в спецификации CDI. Так что, хотя я не могу этого объяснить, я для себя чувствую себя неплохо. У одного из наших приложений также 60 000 пользователей, и оно работает нормально.   -  person Karl Kildén    schedule 14.12.2012


Ответы (4)


Возможно, потокобезопасность для этой ситуации была намеренно исключена из спецификации, что означает, что потокобезопасность не гарантируется.

Давайте подумаем: если поле, написанное одним потоком, читается каким-то другим потоком, если нет какой-либо формы отношения «происходит до», другой поток может прочитать устаревшие данные. В конечном итоге Guice использует либо отражение для установки значения myField, либо может использовать автоматически сгенерированный установщик. Отношения «происходит до» не существует, так что запись-рефлексия происходит до чтения поля или вызов метода происходит до чтения поля (если только не используются блокировки, летучие значения или другие средства, формирующие отношение «происходит-прежде»).

Поэтому я бы сказал, что существует (возможно, довольно низкая) возможность увидеть нулевые значения.

РЕДАКТИРОВАТЬ: согласно http://bit.ly/1m4AUIz запись в финальное поле после завершения конструктора (путем отражения) содержит ту же семантику, что и инициализация поля в конструкторе. Итак, сделайте введенные Guice поля окончательными, установите для них значение null, и все должно работать правильно. Это действительно очень темный угол JVM :-) Более того, согласно http://bit.ly/1m4AwJU Guice выполняет инъекции ровно в один поток, что делает его потокобезопасным... Мне это кажется странным с точки зрения производительности, но, видимо, так оно и работает.

person Martin Vysny    schedule 28.06.2013
comment
Полезно знать, но Guice не является реализацией CDI, и, как объяснено в ответе Роба, CDI исключает внедрение конечных полей. - person HansMari; 14.01.2014

Я считаю, что вы можете на основе раздела 9.1.1 модели памяти Java: http://www.cs.umd.edu/~pugh/java/memoryModel/jsr133.pdf 9.1.1 Модификация конечных полей после построения... Происходит зависание конечного поля как в конце конструктора, в котором установлено конечное поле, и сразу после каждой модификации конечного поля посредством отражения другого специального механизма. ...

Вот некоторые связанные с этим обсуждения Guice: >http://markmail.org/message/fxs5k32dihpoy5ry#query:bob%20lee%20constructor%20injection+page:1+mid:fxs5k32dihpoy5ry+state:results

.. и http://www.theserverside.com/discussions/thread.tss?thread_id=52252#284713

Было бы неплохо, если бы DI-фреймворки сделали это явное заявление.

person Rob Bygrave    schedule 03.07.2013
comment
Хотя кажется, что Guice позволяет использовать внедрение полей в окончательные поля, спецификация CDI специально исключает следующее: Вводимое поле является нестатическим, неконечным полем класса компонента... (3.8). Кроме того, спецификация CDI ничего не говорит о том, выполняется ли инъекция в одном потоке или нет. - person HansMari; 03.07.2013

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

person Steven Schlansker    schedule 14.12.2012
comment
Я тоже, но больше из-за интуиции, чем из твердых знаний. И этот подход заставляет вас предоставлять неуклюжий фиктивный конструктор без аргументов, который просто обнуляет все поля, верно? В противном случае класс не будет проксируемым. - person HansMari; 14.12.2012
comment
Вы можете аннотировать конструктор с помощью @Inject, и он введет все аргументы в конструктор, после чего вы сможете действовать, как обычно. Конечно, любые квалификаторы должны быть в аргументах конструктора. - person LightGuard; 14.12.2012
comment
Я знаю, как работает инъекция конструктора, как указано в исходном вопросе. Но когда я добавляю конструктор с аргументами (аннотированными инъекцией или нет), мне также нужно добавить явный конструктор без аргументов, чтобы сохранить класс проксируемым. А в случае конечных полей мне нужно установить для всех полей что-то (в данном случае null). Это немного неловко. - person HansMari; 14.12.2012
comment
Не могу сказать, что знаю достаточно о проксировании CDI / Weld, чтобы помочь с этим. Тем не менее, я обычно стараюсь использовать только прокси-интерфейсы (а не конкретные классы), и тогда нет необходимости в фиктивных конструкторах. - person Steven Schlansker; 14.12.2012
comment
Хорошая мысль об использовании интерфейсов. Однако иногда они бесполезны и просто добавляют шаблон. - person HansMari; 14.12.2012

Любые риски параллелизма при использовании внедренного экземпляра зависят от эффективной области действия этого экземпляра.

Если MyClass находится в области @Dependent по умолчанию, каждая точка внедрения получит свой собственный экземпляр. Меры предосторожности, которые вы принимаете в отношении безопасности потоков, будут такими же, как если бы вы сами вызвали new MyClass(). Если вы получаете доступ к этому экземпляру из нескольких потоков, вам необходимо убедиться, что MyClass является потокобезопасным, или обеспечить некоторую синхронизацию вокруг него.

Если MyClass находится в более широком диапазоне, таком как @SessionScoped или @ApplicaionScoped, то один и тот же экземпляр (или его прокси) может быть внедрен в несколько точек внедрения в одном и том же контексте. Например, если у вас есть параллельные запросы браузера из одного и того же сеанса, обращающегося к MyClass, а MyClass аннотируется @SessionScoped, у вас может быть несколько потоков, обращающихся к одному и тому же экземпляру параллельно. CDI не собирается синхронизировать это для вас, поэтому вам нужно убедиться, что MyClass является потокобезопасным.

person Brian    schedule 14.12.2012
comment
Мой вопрос не о том, может ли внедренный экземпляр быть доступен для нескольких потоков или нет - я знаю это и действительно хочу этого. Одним из типичных вариантов использования является класс ресурсов JAX-RS с ApplicationScoped, в который вводятся некоторые поля. Конечно, это означает, что многие потоки будут обращаться к одному и тому же экземпляру. Сам MyClass может быть полностью потокобезопасным (например, если доступ ко всем полям защищен блокировкой) и все же может быть опубликован небезопасным способом (см. это). Делает ли CDI что-нибудь по этому поводу? - person HansMari; 14.12.2012