将对象引用存储到易失性字段中

Storing object reference into a volatile field

我正在使用以下字段:

private DateDao dateDao;

private volatile Map<String, Date> dates;

public Map<String, Date> getDates() {
    return Collections.unmodifiableMap(dates);
}

public retrieveDates() {
     dates = dateDao.retrieveDates();
}

在哪里

public interface DateDao {
    //Currently returns HashMap instance
    public Map<String, Date> retrieveDates();
}

这样发布日期地图安全吗?我的意思是,volatile 字段意味着对字段的引用不会缓存在 CPU 寄存器中,也不会在访问时从内存中读取。

因此,我们不妨读取 state of the map 的陈旧值,因为 HashMap 不执行任何同步。

这样做安全吗?

UPD:例如假设 DAo 方法以下列方式实现:

public Map<String, Date> retrieveDates() {
    Map<String, Date> retVal = new HashMap<>();
    retVal.put("SomeString", new Date());
    //ad so forth...
    return retVal;
}

可以看出,Dao方法不做任何同步,HashMapDate都是可变的,不是线程安全的。现在,我们已经创建并发布了它们,如上所示。是否保证其他线程从 dates 读取的任何后续内容不仅会观察到对 Map 对象的正确引用,而且还会观察到它的“新鲜”状态。

我不确定线程​​是否无法观察到一些陈旧的值(例如 dates.get("SomeString") returns null

据我所知,声明地图 volatile 不会同步其访问(即 readers 可以在 dao 更新地图时读取地图)。但是,它保证映射存在于共享内存中,因此每个线程在每个给定时间都会在其中看到相同的值。当我需要同步和新鲜度时,我通常做的是使用锁对象,类似于以下内容:

private DateDao dateDao;
private volatile Map<String, Date> dates;
private final Object _lock = new Object();

public Map<String, Date> getDates() {
   synchronized(_lock) {
     return Collections.unmodifiableMap(dates);
   }
}

public retrieveDates() {
   synchronized(_lock) {
     dates = dateDao.retrieveDates();
   }
}

这提供了 readers/writers 同步(但请注意,作者没有优先级,即如果 reader 正在获取地图,作者将不得不等待)和 'data freshness' 通过 volatile。此外,这是一种非常基本的方法,还有其他方法可以实现相同的功能(例如 Locks 和 Semaphores),但大多数时候这对我有用。

我认为你在问两个问题:

  1. 鉴于该 DAO 代码,您使用它的代码是否可以使用它在此处获取的对象引用:

     dates = dateDao.retrieveDates();
    

before 引用的 dateDao.retrieveDates 方法已完成添加到该对象。例如,内存模型的语句重新排序语义是否允许 retrieveDates 方法在最后一个 put(等)完成之前 return 引用?

  1. 一旦您的代码具有 dates 引用,您的代码中是否存在对 dates 的非同步访问以及通过 read-only 视图的问题 return 来自 getDates.

你的领域是否 volatile 与这两个问题都没有关系。使您的字段 volatile 所做的唯一一件事是阻止调用 getDates 的线程为您的 dates 字段获取 out-of-date 值。即:

Thread A                                       Thread B
----------                                     --------
1. Updates `dates` from dateDao.retrieveDates
2. Updates `dates` from " " again
3.                                             getDates returns read-only
                                               view of `dates` from #1

如果没有 volatile,上述情况是可能的(但无害)。对于 volatile,它不是,线程 B 将看到来自 #2 而不是 #1 的 dates 的值。

但这与我认为您要问的任何一个问题都不相关。

问题 1

不,您在 retrieveDates 中的代码无法在 dateDao.retrieveDates 填写该地图之前看到 dateDao.retrieveDates 编辑的对象引用 return。 memory model 允许重新排序语句,但是:

...compilers are allowed to reorder the instructions in either thread, when this does not affect the execution of that thread in isolation

(我的重点。)dateDao.retrieveDates之前返回对代码的引用显然会影响隔离线程的执行。

问题 2

您显示的 DAO 代码永远无法修改它 return 给您的映射,因为它不保留它的副本,因此我们无需担心 DAO。

你的代码中,你没有显示任何修改dates内容的内容。如果你的代码没有修改 dates 的内容,那么就不需要同步,因为地图是不变的。当您 获取 时,您可能希望通过将 dates 包装在 read-only 视图中来保证这一点,而不是当您 return 它时:

dates = Collection.unmodifiableMap(dateDao.retrieveDates());

如果您的代码 修改了 dates 某个您没有显示的地方,那么是的,可能会出现问题,因为 Collections.unmodifiableMap 不会同步地图操作。它只是创建一个 read-only 视图。

如果您想确保同步,您需要将 dates 包装在 Collections.synchronizedMap 实例中:

dates = Collections.synchronizedMap(dateDao.retrieveDates());

然后在您的代码中对它的所有访问都将被同步,并且通过您 return 的 read-only 视图对它的所有访问也将被同步,因为它们都经过同步映射。