最近我读了a great tutorial of Java Memory Model 。它说 JVM 只保证 如果未使用同步,则 final 字段的可见性。然后我突然想到,当我们使用一些 IoC 框架时,我们通常使用不受 final 语义保护的 setter 注入(inject)/字段注入(inject)。例如,
class SomeController {
private SomeService service;
@Inject
public void setService(SomeService s){
this.service = s;
}
}
注入(inject)后某个线程是否有可能读取 service
的过时值?或者我们应该将 service
标记为 volatile 字段?
最佳答案
首先,您正在阅读一个非常古老的“教程”(对于如此复杂的主题来说,这是一个相当奇怪的名称)。此外,该文档针对的是(通常)编写编译器或围绕 JVM 本身进行工作的人员;我仍然认为这是一篇很棒的文章。
您是对的,在特殊条件下可以保证可见性;但 final
只是其中之一。至少有3个(且不限于):
使用适当的锁定字段
使用静态初始化器
使用
volatile
字段。
最后,这称为“安全发布”,它是关于调用者如何在给定 SomeController
实例的引用的情况下感知其字段(service
) >)。他们能保证看到非空服务
吗?
Spring 保证它将是一个完全初始化的实例,但不是您想象的那样。 JLS
中有一个称为“happens-before”的原则。它也被称为“发生在”之前的“关系”,因为它涉及两方。例如,一个执行写入操作(调用 setService
),另一个执行读取操作(使用该service
)。据说,当双方都遵循某些规则时,这种关系就得到了保证和满足(阅读部分看到一个非空的服务
)。这些规则写得非常严格in the JLS 。简而言之:仅当遵循这些规则之一时,您才能保证看到非空服务
。您提到了其中之一:
A write to a volatile field happens-before every subsequent read of that field.
但请注意,它并不是唯一的一个。
因此,例如,如果 Spring 在一个线程中执行所有注入(inject),并且仅在其上下文上调用 Thread::start
后进行,那么就有一个规则in the JLS here
A call to start() on a thread happens-before any actions in the started thread.
这将保证 service
被注入(inject)并正确地视为非空。
这里可能需要更多解释,所以这里是一个例子:
// (1) init Spring context and do the needed injections
// (2) call Thread::start with this context
// (3) use context in a different thread now
我们需要遵循 JLS 文档中的三个规则:
If x and y are actions of the same thread and x comes before y in program order, then hb(x, y).
这意味着 (1) 发生在 (2) 之前
A call to start() on a thread happens-before any actions in the started thread.
这意味着 (2) 发生在 (3) 之前。
If hb(x, y) and hb(y, z), then hb(x, z).
这意味着 (1) 发生在 (3) 之前。这就是我们关心的,这只是 Spring 实现适当可见性的一种方式。
关于java - java IoC框架如何保证线程安全?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63869205/