假设我有一个高读取,低写入并且需要存储应用程序数据的并发映射:
ConcurrentMap<UUID, Data> map = new ConcurrentHashMap<UUID, Data>();
然后,在启动期间和通过用户输入,数据被添加到地图中:
public void createData(Data newData) {
map.put(newId, newData); // etc...
}
如果我需要更改数据,我应该:
A)使Data类对象不可变,然后每次需要对Data对象进行更改时执行put操作:
public void changeData(UUID oldId, Foo newInfo) {
Data oldData = map.get(oldId);
Data newData = new Data(oldData, newInfo); // Constructor for demo only
map.put(newData);
saveToDatabase(newData);
}
B)使用volatile字段,原子引用或最终并发字段使Data类对象可变但是线程安全,并根据需要简单地修改对象:
public void changeData(UUID oldId, Foo newInfo) {
Data data = map.get(id);
data.changeSomething(newInfo);
saveToDatabase(data);
}
C)以上都不是
答案 0 :(得分:7)
A)是更好的选择,原因有两个:
volatile
)会对您不利。ConcurrentHashMap
几乎无法让您的生活更轻松。答案 1 :(得分:3)
只是一个想法。您声明写入速率很低,但是为了参数,我们假设changeData
方法有多个并发写入/调用。然后,调用该方法的线程可能最后完成(在两种方法中)。
如果您的应用程序逻辑假定将遵循插入顺序,则可能会产生错误的结果。在这种情况下,方法changeData
的主体是您的critical section,其中每个定义意味着它不应该同时执行。
关键部分定义对应用程序域语义和代码结构非常敏感,因此我无法确定该方法是是否被视为关键部分。通过变量的名称进行猜测,并假设您的地图是来自数据库的用户数据缓存,我猜你可以忽略这个答案。但做仔细考虑它,但是:)
如果所有的写操作都通过这个方法,那么这将是代码草图(你可以使用非线程安全的地图实现):
public void changeData(UUID oldId, Foo newInfo) {
synchronized(SomeClass.class) { // global lock
//update logic
}
}
这只是一个草图来说明当然。如果 问题,你很可能会使用一些Java并发结构。
答案 2 :(得分:2)
如果您可以选择创建不可变类,那么实施#A
会更好:就地修改很难实现和维护。
有时候去不可变路线可能不是一个选择,因为需要经常修改一个相对较大的对象。在这种情况下,您可能需要重新考虑并发哈希映射到您的设计的应用程序,因为它是同步的事实并没有给您太多的优势。