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

时间:2016-01-22 09:53:04

标签: java multithreading volatile

我正在使用以下字段:

private DateDao dateDao;

private volatile Map<String, Date> dates;

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

public retireveDates(){
     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")返回null

2 个答案:

答案 0 :(得分:1)

据我所知,声明地图volatile不会同步其访问权限(即读者可以在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 retireveDates() {
   synchronized(_lock) {
     dates = dateDao.retrieveDates();
   }
}

这为读者/作者提供同步(但请注意,编写者没有优先权,即如果读者正在获取作者必须等待的地图)和“数据新鲜度”通过volatile。此外,这是一个非常基本的方法,还有其他方法可以实现相同的功能(例如LockSemaphores),但大多数情况下这对我来说都是诀窍。

答案 1 :(得分:1)

我想你提出两个问题:

  1. 鉴于DAO代码,使用它的代码是否可以使用对象引用,它可以在这里找到:

    dates = dateDao.retrieveDates();
    

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

  2. 一旦您的代码具有dates引用,是否存在代码中dates的非同步访问权限问题,以及通过{{1}返回的只读视图的问题}}

  3. 您的字段getDates是否与这两个问题无关。使您的字段volatile执行的唯一操作是阻止调用volatile的线程获取getDates字段的过期值。那就是:

    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
    

    如果没有dates,上面的场景是可能的(但无害)。对于volatile,它不是,线程B将从#2看到volatile的值,而不是#1。

    但这与我认为你要问的任何一个问题无关。

    问题1

    不,dates(原文如此)中的代码在retireveDates填写该地图之前无法看到dateDao.retrieveDates返回的对象引用。 memory model允许重新排序语句,但是:

      

    ...允许编译器对任一线程中的指令进行重新排序,当这不会影响该线程的独立执行时

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

    问题2

    您显示的DAO代码永远不会修改它返回给您的地图,因为它没有保留它的副本,所以我们不需要担心DAO。

    您的代码中,您没有显示任何修改dateDao.retrieveDates内容的内容。如果您的代码没有修改dates的内容,那么就不需要同步,因为地图是不变的。您可能希望通过在获取时将dates包装在只读视图中而不是在返回时将其作为保证:

    dates

    如果您的代码 修改dates = Collection.unmodifiableMap(dateDao.retrieveDates()); 某个您尚未显示的地方,那么是的,可能会出现问题,因为dates无法同步地图操作。它只是创建一个只读视图。

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

    dates

    然后,您的代码中对它的所有访问都将被同步,并且通过您返回的只读视图对它的所有访问也将被同步,因为它们都通过同步映射。