Java 注入(@Inject)字段是否安全发布?

Java 注入(@Inject)字段是否安全发布?,java,thread-safety,cdi,weld,Java,Thread Safety,Cdi,Weld,当我在类中使用字段注入时,如下所示: @Inject private MyClass myField; 我可以对该字段的“”状态做出任何假设吗?或者换一种说法,假设MyClass本身是线程安全的,那么在使用此字段时是否存在任何并发风险 如果可能的话,我的直觉通常是创建所有字段final,但这不适用于字段注入。当然,我可以使用构造函数注入,但是我通常不得不创建一个额外的“假”无参数构造函数来进行代理。这不是什么大问题,但使用现场注入更方便。另一种选择是将字段标记为volatile(或者甚至对其使

当我在类中使用字段注入时,如下所示:

@Inject
private MyClass myField;
我可以对该字段的“”状态做出任何假设吗?或者换一种说法,假设MyClass本身是线程安全的,那么在使用此字段时是否存在任何并发风险

如果可能的话,我的直觉通常是创建所有字段final,但这不适用于字段注入。当然,我可以使用构造函数注入,但是我通常不得不创建一个额外的“假”无参数构造函数来进行代理。这不是什么大问题,但使用现场注入更方便。另一种选择是将字段标记为volatile(或者甚至对其使用锁…),但这真的有必要吗

JSR-299规范似乎没有回答这个问题。我在像Weld这样的实现中使用CDI

  • 我注入的对象将被多个线程使用(例如@ApplicationScoped)。我想要这个
  • 我理解,如果MyClass是不可变的,那么安全发布就不成问题。但我不一定只注入不可变的对象
  • MyClass本身被认为是线程安全的;这与我无关。严格来说,问题在于不安全的发布,例如,由于Java内存模型的规则,线程可能会看到半构造的MyClass实例

我总是使用构造函数注入。那么,您的字段可能是最终字段,它们的线程安全性毫无疑问。

使用注入实例时的任何并发风险都取决于该实例的有效范围

如果
MyClass
在默认的
@Dependent
范围内,每个注入点将获得自己的实例。关于线程安全,您所采取的预防措施与您自己调用
new MyClass()
时所采取的预防措施相同。如果您从多个线程访问该实例,则需要确保
MyClass
是线程安全的,或者围绕它提供一些同步


如果
MyClass
在更广的范围内,如
@SessionScoped
@applicationscoped
,则可以将同一实例(或其代理)注入同一上下文中的多个注入点。例如,如果有来自同一会话的并行浏览器请求访问
MyClass
,并且
MyClass
被注释为
@SessionScoped
,则可能有多个线程并行访问同一实例。CDI不会为您提供任何同步,因此您必须确保
MyClass
是线程安全的。

可能这种情况下的线程安全性被故意从规范中删除,这意味着线程安全性没有得到保证

让我们思考一下:如果一个线程编写的字段被另一个线程读取,除非存在某种形式的before关系,否则另一个线程可能会读取过时的数据。Guice最终使用反射来设置myField的值,也可以使用自动生成的setter。没有发生在之前的关系,因此反射写入发生在字段读取之前,或者方法调用发生在字段读取之前(除非使用了锁、挥发物或其他形成发生在之前关系的方法)

因此,我想说的是,出现空值的可能性(可能相当低)

编辑:根据构造函数结束后写入最终字段(通过反射),与在构造函数中初始化字段具有相同的语义。所以,将Guice注入的字段设为final,将它们设置为null,它应该可以正常工作。这确实是一个非常黑暗的JVM角落:-)
此外,根据Guice,只在一个线程中执行注入,这使其线程安全。。。在性能方面,我觉得很奇怪,但显然它是这样工作的。

我相信您可以基于java内存模型的9.1.1节:9.1.1最终字段的构造后修改最终字段冻结发生在设置最终字段的构造函数的末尾,以及通过反射对最终字段进行每次修改后立即发生

一些相关的Guice讨论包括:

。。及


如果DI框架能做出这样明确的声明,那就太好了。

我也是,但更多的是出于直觉而非扎实的知识。这种方法迫使您提供一个笨拙的“虚拟”无参数构造函数,它只是将所有字段置空,对吗?否则该类将不可代理。您可以使用
@Inject
注释构造函数,它将所有参数注入构造函数,然后您可以像平常一样继续。当然,任何限定符都需要在构造函数args上。我知道构造函数注入是如何工作的,如原始问题中所述。但是,当我添加一个带有参数的构造函数(无论是否带有注入注释)时,我还需要添加一个显式的无参数构造函数来保持类的可代理性。对于final字段,我需要将所有字段设置为某个值(在本例中为null)。这有点尴尬。我不能说我对CDI/焊接代理了解得足够多来帮助解决这个问题。也就是说,我通常只尝试代理接口(而不是具体的类),这样就不需要虚拟构造函数了。使用接口的好处是。然而,有时它们并不有用,只是简单地添加了样板文件。是的,使用CDI有时需要在使用访问修饰符和final时编写一些不那么防御性的代码。但我不认为线程安全会是CDI-spec中的一个疏忽,所以尽管我无法解释这一点,但我自己感觉非常安全。我们的一个应用程序也有60000个用户,正在解决这个问题。我的问题不是一个注入的实例是否可以被多个线程访问——我知道这一点,而且我确实想要它。一个典型的用例是JAX-RS资源类