【问题标题】:Safe publication example in Java Concurrency in PracticeJava并发实践中的安全发布示例
【发布时间】:2017-10-08 13:29:46
【问题描述】:

Java Concurrency in Practice 说,您可以安全地发布一个有效的不可变对象(例如,您构造并且不再更改的 Date 对象),方法是将其粘贴到如下所示的同步集合中(摘自本书,第 53 页):

public Map<String, Date> lastLogin =
    Collections.synchronizedMap(new HashMap<String, Date>())

我了解,任何放入此映射的 Date 对象在放入此同步映射后都将是可见的(至少在其初始但完全构造的状态下),但只有在其他线程可以获得对此 Map 对象的引用时。

由于引用字段 lastLogin 没有保证可见性的字段属性(final、volatile、guarded 或由静态初始化程序初始化),我认为地图本身可能不会以完全构造的状态显示到其他线程,因此本末倒置。还是我错过了什么?

【问题讨论】:

  • 顺便说一句... (a) java.time 类,例如 InstantLocalDateZonedDateTime 确实是不可变的和线程安全的。所以无需构建自己的日期类。 ☺ (b) 避免使用麻烦的遗留类,如 java.util.DateCalendar,因为它们不是线程安全的并且还有许多其他问题。 ☹
  • 我认为它可以工作,因为同步的集合在某些时候会调用同步,这总是使完整状态对其他线程可见。
  • 假设 lastLogin 本身是安全发布的;他想要得到的是,一旦你有了对地图的参考,你就可以用它来安全地发布其他参考。

标签: java multithreading


【解决方案1】:

您的怀疑是对的,因为不能保证lastLogin 的值对其他线程可见。因为lastLogin 不是volatilefinal,所以另一个线程可能会将其读取为null

但是,您不必担心其他线程会看到地图的不完整版本。 Collections.synchronizedMap(...) 返回带有 final 字段的 a private class 实例。 JLS section 17.5 说:

final 字段的使用模型很简单:在对象的构造函数中设置对象的 final 字段;并且不要在对象的构造函数完成之前在另一个线程可以看到它的地方写入对正在构造的对象的引用。如果遵循这一点,那么当另一个线程看到该对象时,该线程将始终看到该对象的最终字段的正确构造版本。

SynchronizedMap 遵循这些规则,因此读取lastLogin 的另一个线程将读取null 或对完全构造的地图的引用,而不是对地图的不完整或不安全版本的引用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-04-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-22
    • 2018-11-06
    • 1970-01-01
    相关资源
    最近更新 更多