java IoC框架如何确保线程安全?

时间:2020-09-13 09:31:50

标签: java spring concurrency inversion-of-control java-memory-model

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

class SomeController {
    private SomeService service;

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

是否在注入后某些线程可能读取过时的值service?还是应该将service标记为 volatile 字段?

1 个答案:

答案 0 :(得分:3)

首先,您正在阅读的“教程”(对于这样一个复杂的主题来说是一个很奇怪的名称),它真的很古老了。此外,该文档还针对(通常)编写编译器或围绕JVM本身工作的人员。我仍然觉得这是一篇很棒的文章。

您肯定在特殊条件下保证了可见性;但是final只是其中之一。至少有3个(但不限于):

  • 使用适当的锁定字段

  • 使用静态初始化程序

  • 使用volatile字段。

最后,这称为“安全发布”,这全都涉及调用者在引用SomeController实例的情况下如何感知其字段(service)。是否可以保证看到非空的service

Spring保证它将是一个完全初始化的实例,但并非您可能认为的那样。 JLS中有一个称为“ happens-before”的原则。它也称为“关系”之前的事件,因为它涉及两个方面。例如,一个执行写操作(调用setService),一个执行读操作(使用service)。据说当双方都遵循某些规则时,关系就得到了保证和满足(阅读部分看到一个非空service)。这些规则非常严格地写成in the JLS。用更简单的话来说:当遵循其中一个规则时,可以保证只看到非null service 。您提到其中之一:

对易失性字段的写操作发生在每次对该字段的后续读取之前。

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

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

在启动线程中的任何操作之前,都会在线程上进行对start()的调用。

这将确保注入service并正确地将其视为非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文档中需要遵循以下三个规则:

如果x和y是同一线程的动作,并且x按程序顺序位于y之前,则hb(x,y)。

这意味着(1)发生在(2)之前

在启动线程中的任何操作之前,都会在线程上进行对start()的调用。

这意味着(2)发生在(3)之前。

如果hb(x,y)和hb(y,z),则hb(x,z)。

这意味着(1)发生在-(3)之前。这是我们关心的问题,这只是Spring获得适当可见性的一种方法。

相关问题