java IoC框架是如何保证线程安全的?

How does java IoC framework ensure thread safety?

最近看了a great tutorial of Java Memory Model。它说 JVM 只保证 如果未使用同步,final 字段的可见性。然后我想到,当我们使用一些 IoC 框架时,我们通常使用不受 final 语义保护的 setter injection/field 注入。例如,

class SomeController {
    private SomeService service;

    @Inject
    public void setService(SomeService s){
        this.service = s;
    }
}

是否有可能某些线程在注入后读取 service 的陈旧值?或者我们应该将 service 标记为 volatile 字段?

首先,您正在阅读 确实 过时的“教程”(对于如此复杂的主题来说,这是一个相当奇怪的名称)。此外,该文档针对的是(通常)编写编译器或围绕 JVM 本身工作的人;我仍然觉得它很棒 write-up.

你说得对,在特殊条件下能见度是有保障的;但 final 只是其中之一。至少有3个(不限于):

  • 使用适当的锁定字段

  • 使用静态初始化器

  • 使用 volatile 字段。

最后,这称为“安全发布”,它是关于调用者如何在给定对 SomeController 实例的引用的情况下感知其字段 (service) 的。他们能保证看到 non-null service 吗?

Spring 保证它将是一个完全初始化的实例,但不是您可能认为的那种感觉。 JLS里面有个原则叫“happens-before”。它也被称为 happens-before“关系”,因为它涉及两方。例如,一个执行写入(调用 setService)和一个执行读取(使用 service)。据说当双方都遵守一些规则时,这种关系就会得到保证和履行(阅读部分看到non-null service)。这些规则写得非常严格in the JLS。简而言之:当遵循其中一条规则时,您一定会看到 non-null service 只有 。您提到其中之一:

A write to a volatile field happens-before every subsequent read of that field.

但请注意,它不是那里唯一的一个。

因此,如果 Spring,例如,在线程中执行所有注入,并且只有 after 在其上下文中调用 Thread::start,那么有一条规则 in the JLS here

A call to start() on a thread happens-before any actions in the started thread.

这将保证 service 被注入并正确地被视为 non-null。


这可能需要更多的解释,所以这里有一个例子:

// (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) happens-before (2)

A call to start() on a thread happens-before any actions in the started thread.

这意味着 (2) happens-before (3).

If hb(x, y) and hb(y, z), then hb(x, z).

这意味着 (1) happens-before (3)。这是我们关心的问题,它只是 Spring 实现适当可见性的一种方式。