当前位置: 首页 > 面试题库 >

如果在Spring Framework的@PostConstruct中初始化对象属性,是否应该将对象属性标记为volatile?

司雅畅
2023-03-14
问题内容

假设我在Spring单例bean @PostConstruct(简化代码)中进行了一些初始化:

@Service
class SomeService {
  public Data someData; // not final, not volatile

  public SomeService() { }

  @PostConstruct
  public void init() {
     someData = new Data(....);
  }
}

我应该担心someData其他豆的可见性并对其进行标记volatile吗?

(假设我无法在构造函数中初始化它)

第二种情况:如果我 覆盖 in的值@PostConstruct(例如在显式初始化或构造函数中的初始化之后),那么@PostConstruct
将不会首先写入 该属性怎么办?


问题答案:

Spring框架与Java编程语言无关,它只是一个框架。因此,通常,您需要将不同线程访问的非final字段标记为volatile。归根结底,Spring
bean只不过是Java对象,所有语言规则都适用。

final使用Java编程语言对字段进行特殊处理。亚历山大Shipilev,在 Oracle的性能的家伙
,写了一大篇关于此事。简而言之,当构造函数初始化final字段时,用于设置字段值的程序集会添加一个附加的内存屏障,以确保任何线程都能正确看到该字段。

对于非final字段,不会创建此类内存屏障。因此,通常,@PostConstruct-annotated方法很可能会初始化该字段,并且另一个线程不会看到此值,或者更糟的是,在构造函数仅部分执行时看到该值。

这是否意味着您始终需要将非final字段标记为易失性?

简而言之,是的。如果一个字段可以由不同的线程访问,则可以。不要犯与我只想了几秒钟(感谢Jk1进行更正)并考虑Java代码的执行顺序有关的错误。您可能会认为您的Spring应用程序上下文是在单个线程中引导的。这意味着引导线程将不会对非易失性字段产生问题。因此,您可能会认为一切正常,只要您不将应用程序上下文公开给另一个线程,直到它完全初始化即可,即调用带注释的方法。这样想,您可以假设,其他线程只要没有在引导后更改字段,就没有机会缓存
错误的 字段值。

相反,允许编译后的代码对指令进行重新排序,即,即使@PostConstruct在相关bean暴露给Java代码中的另一个线程之前调用-
annotated方法,也不一定会在编译后的代码中保留这种 巧合之前的 关系。运行。因此,另一个线程可能 始终
读取并缓存该非volatile字段,而该非字段甚至还没有被完全初始化,甚至被部分初始化。这可能会引入一些细微的错误,不幸的是,Spring文档并未提及此警告。JMM的这些细节是我个人更喜欢final字段和构造函数注入的原因。

更新 :根据另一个问题的答案,在某些情况下,未将字段标记为volatile仍会产生有效结果。我对此进行了进一步的研究,事实上Spring框架保证了一定数量的
事情发生-
开箱即用的安全性。看看JLS的事前发生关系,其中明确指出:

监视器上的解锁发生在该监视器上的每个后续锁定之前。

Spring框架利用了这一点。所有bean都存储在一个映射中,并且每次在该映射中注册或检索bean时,Spring都会获取一个特定的监视器。结果,在注册完全初始化的bean之后,同一监视器将被解锁,并在从另一个线程检索同一bean之前将其锁定。这迫使另一个线程遵守Java代码的执行顺序所反映
的事前发生
关系。因此,如果您一次引导您的bean,则所有访问完全初始化的bean的线程都将看到此状态,只要它们以规范的方式访问该bean(即通过查询应用程序上下文或自动编写来进行显式检索)。例如,@PostConstruct即使不声明字段,也可以保证二传手注入或方法的使用安全volatile。实际上,因此您应该避免使用volatile字段,因为它们会为每次读取引入运行时开销,从而导致在循环访问字段时会感到痛苦,并且由于关键字表示错误的意图。(顺便说一句,据我所知,Akka框架采用了类似的策略,其中除Spring之外,Akka
在此问题上有所保留。)

但是,此保证仅用于在引导程序之后对Bean的检索。如果volatile在引导后更改了非字段,或者在初始化期间泄漏了bean引用,则该保证不再适用。

请查看此较早的博客条目,该条目进一步详细描述了此功能。显然,该功能并未得到记录,因为即使Spring人士也知道(但是很长时间没有做任何事情)。



 类似资料:
  • 问题内容: 为了我的一生,我无法弄清楚下面的C#代码示例中发生了什么。测试类的collection(List)属性设置为只读,但是我似乎可以在对象初始化器中为其分配值。 **编辑:修复了列表’getter’的问题 问题答案: 这个: 译成: 它从不尝试调用setter,而只是在调用 getter 的结果上调用。请注意,它也不会 清除 原始集合。 C#4规范的7.6.10.3节对此进行了详细描述。

  • 问题内容: 有人可以给我展示一种代码有效的方法,以 根据从超链接发送给它的参数在spring mvc中更改对象属性吗? 我正在修改spring petclinic示例应用程序,以便“所有者” 详细信息页面可以显示特定 “所有者”拥有的每种“宠物”的单独列表。当前,“宠物”列表是每个“所有者”的属性, 可以在jstl中作为owner.pets访问。我想要的是让我的 jstl代码能够从jstl调用ow

  • 我想接收一些输入字段的值,并将它们设置为newValue状态,但状态的某些属性本身就是对象。希望newValue状态的格式为: 现在,对象保存如下: 有没有办法通过调整getValue函数或输入字段来实现这一点?

  • 我正在使用一个映射,并希望使用一个值对象作为映射键。。和一个列表作为值。值对象有两个属性:第一个名称、第二个名称。。我想把地图还给你。如果两个属性都与同一映射中的某个键匹配,则containsKey()为true。。 我尝试使用比较器如下 这是我正在使用的值对象 但它为我返回假...请帮助我...谢谢提前

  • 问题内容: 有两个JPA实体:具有一对多关系的用户和订单。 我在每个方法都在事务中运行的服务层类中使用这些实体。一切都很好,除非服务层类的方法必须返回这些实体。 此方法可以很好地返回数据。但是,当我尝试访问接收到的集合元素时,我捕获到异常:“ org.hibernate.LazyInitializationException:无法延迟初始化角色集合:package.User.orders,没有会话