在ConcurrentHashMap中修改值的首选方法是什么?
比方说,我有一个并发地图是高读取,低写入,并需要存储应用程序数据:在ConcurrentHashMap中修改值的首选方法是什么?
ConcurrentMap<UUID, Data> map = new ConcurrentHashMap<UUID, Data>();
,然后在启动过程中,通过用户输入的数据添加到地图:
public void createData(Data newData) {
map.put(newId, newData); // etc...
}
如果我那么需要改变的数据,我应该:
A)使数据类对象不变,然后在每次需要针对数据对象发生变化时进行卖出操作:
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)使数据类对象可变但线程安全挥发性字段,原子的引用或最终并发领域,并简单地修改为所需要的对象:
public void changeData(UUID oldId, Foo newInfo) {
Data data = map.get(id);
data.changeSomething(newInfo);
saveToDatabase(data);
}
C)的上述无
A)是更好的选择,原因有二:
- 由于在方案中读取较为频繁,就应该减少对他们的开销量。在这种情况下添加额外的同步(如
volatile
)对您不利。 - 通过使用带有额外自定义保护措施(可能有错误)的可变对象,您几乎可以通过使用
ConcurrentHashMap
来挫败让自己的生活更轻松的观点。
如果您可以选择创建一个不可变的类,那么执行#A
的效果会更好:就地修改的实现和维护非常困难。
由于需要对相对较大的对象进行频繁修改,因此有时候不可变的路由可能不是一个选项。在这种情况下,您可能希望重新考虑并发哈希映射对您的设计的应用,因为它是同步的并不会给您带来太多优势。
虽然我接受了另一个答案,因为它帮助我更多地了解了这个问题,但是我感谢你的回答,尽可能将大型可变对象和它们的位置(不在并发散列图中)固定在其他思想上。谢谢。 – oberger
只是一个想法。您声明写入速率较低,但为了说明方便,我们假设changeData
方法的多个并发写入/调用。然后可能的是,调用方法的线程是最后一个,首先完成(在两种方法中)。
如果您的应用程序逻辑假定插入的顺序将得到遵守,它可能会产生错误的结果。在这种情况下,方法changeData
的主体是critical section,它的定义意味着它不应该被同时执行。
关键部分定义对应用程序域语义和代码结构高度敏感,所以我不能确定该方法是否为被视为关键部分。猜测变量的名称,并假设你的地图是来自数据库的用户数据缓存,我猜你可以忽略这个答案。但是做仔细想起来,虽然:)
如果所有的写操作都要经过这种方法,这将是代码的草图(可以使用非线程安全的地图实现,那么):
public void changeData(UUID oldId, Foo newInfo) {
synchronized(SomeClass.class) { // global lock
//update logic
}
}
这只是一个草图来说明当然点。你最可能使用一些Java并发结构,如果这个是的问题。
取决于你想实现的目标:) – zapl
我想实现一个与ConcurrentHashMap的保证一致的get/put关系,但我只是不确定进行对象操作/创建的顺序或正确的方式。 – oberger
如果您使用不可变的路线并拥有多个写入线程,则可能需要使用replace方法而不是put来确保您正在替换修改的值。 –