我正在使用以下字段:
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方法不进行任何同步,HashMap
和Date
都是可变的,而不是线程安全的。现在,我们已经创建并发布了它们,如上所示。是否保证来自另一个线程的dates
的任何后续读取不仅会观察到对Map
对象的正确引用,而且还会观察到它是“新鲜”状态。
我不确定线程是否无法观察到一些过时的值(例如dates.get("SomeString")
返回null
)
答案 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
。此外,这是一个非常基本的方法,还有其他方法可以实现相同的功能(例如Lock
和Semaphores
),但大多数情况下这对我来说都是诀窍。
答案 1 :(得分:1)
我想你提出两个问题:
鉴于DAO代码,使用它的代码是否可以使用对象引用,它可以在这里找到:
dates = dateDao.retrieveDates();
之前引用的dateDao.retrieveDates
方法已完成添加到该对象。例如,内存模型'语句重新排序语义是否允许retrieveDates
方法在最后put
(等)完成之前返回引用?
一旦您的代码具有dates
引用,是否存在代码中dates
的非同步访问权限问题,以及通过{{1}返回的只读视图的问题}}
您的字段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。
但这与我认为你要问的任何一个问题无关。
不,dates
(原文如此)中的代码在retireveDates
填写该地图之前无法看到dateDao.retrieveDates
返回的对象引用。 memory model允许重新排序语句,但是:
...允许编译器对任一线程中的指令进行重新排序,当这不会影响该线程的独立执行时
(我的重点。)在dateDao.retrieveDates
之前返回对代码的引用显然会影响线程的执行。
您显示的DAO代码永远不会修改它返回给您的地图,因为它没有保留它的副本,所以我们不需要担心DAO。
在您的代码中,您没有显示任何修改dateDao.retrieveDates
内容的内容。如果您的代码没有修改dates
的内容,那么就不需要同步,因为地图是不变的。您可能希望通过在获取时将dates
包装在只读视图中而不是在返回时将其作为保证:
dates
如果您的代码 修改dates = Collection.unmodifiableMap(dateDao.retrieveDates());
某个您尚未显示的地方,那么是的,可能会出现问题,因为dates
无法同步地图操作。它只是创建一个只读视图。
如果您想确保同步,则需要将Collections.unmodifiableMap
包裹在Collections.synchronizedMap
实例中:
dates
然后,您的代码中对它的所有访问都将被同步,并且通过您返回的只读视图对它的所有访问也将被同步,因为它们都通过同步映射。